diff --git a/3rdparty/class.phpmailer.php b/3rdparty/class.phpmailer.php new file mode 100644 index 0000000000..4589cd791e --- /dev/null +++ b/3rdparty/class.phpmailer.php @@ -0,0 +1,2473 @@ +exceptions = ($exceptions == true); + } + + /** + * Sets message type to HTML. + * @param bool $ishtml + * @return void + */ + public function IsHTML($ishtml = true) { + if ($ishtml) { + $this->ContentType = 'text/html'; + } else { + $this->ContentType = 'text/plain'; + } + } + + /** + * Sets Mailer to send message using SMTP. + * @return void + */ + public function IsSMTP() { + $this->Mailer = 'smtp'; + } + + /** + * Sets Mailer to send message using PHP mail() function. + * @return void + */ + public function IsMail() { + $this->Mailer = 'mail'; + } + + /** + * Sets Mailer to send message using the $Sendmail program. + * @return void + */ + public function IsSendmail() { + if (!stristr(ini_get('sendmail_path'), 'sendmail')) { + $this->Sendmail = '/var/qmail/bin/sendmail'; + } + $this->Mailer = 'sendmail'; + } + + /** + * Sets Mailer to send message using the qmail MTA. + * @return void + */ + public function IsQmail() { + if (stristr(ini_get('sendmail_path'), 'qmail')) { + $this->Sendmail = '/var/qmail/bin/sendmail'; + } + $this->Mailer = 'sendmail'; + } + + ///////////////////////////////////////////////// + // METHODS, RECIPIENTS + ///////////////////////////////////////////////// + + /** + * Adds a "To" address. + * @param string $address + * @param string $name + * @return boolean true on success, false if address already used + */ + public function AddAddress($address, $name = '') { + return $this->AddAnAddress('to', $address, $name); + } + + /** + * Adds a "Cc" address. + * Note: this function works with the SMTP mailer on win32, not with the "mail" mailer. + * @param string $address + * @param string $name + * @return boolean true on success, false if address already used + */ + public function AddCC($address, $name = '') { + return $this->AddAnAddress('cc', $address, $name); + } + + /** + * Adds a "Bcc" address. + * Note: this function works with the SMTP mailer on win32, not with the "mail" mailer. + * @param string $address + * @param string $name + * @return boolean true on success, false if address already used + */ + public function AddBCC($address, $name = '') { + return $this->AddAnAddress('bcc', $address, $name); + } + + /** + * Adds a "Reply-to" address. + * @param string $address + * @param string $name + * @return boolean + */ + public function AddReplyTo($address, $name = '') { + return $this->AddAnAddress('ReplyTo', $address, $name); + } + + /** + * Adds an address to one of the recipient arrays + * Addresses that have been added already return false, but do not throw exceptions + * @param string $kind One of 'to', 'cc', 'bcc', 'ReplyTo' + * @param string $address The email address to send to + * @param string $name + * @return boolean true on success, false if address already used or invalid in some way + * @access protected + */ + protected function AddAnAddress($kind, $address, $name = '') { + if (!preg_match('/^(to|cc|bcc|ReplyTo)$/', $kind)) { + $this->SetError($this->Lang('Invalid recipient array').': '.$kind); + if ($this->exceptions) { + throw new phpmailerException('Invalid recipient array: ' . $kind); + } + echo $this->Lang('Invalid recipient array').': '.$kind; + return false; + } + $address = trim($address); + $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim + if (!self::ValidateAddress($address)) { + $this->SetError($this->Lang('invalid_address').': '. $address); + if ($this->exceptions) { + throw new phpmailerException($this->Lang('invalid_address').': '.$address); + } + echo $this->Lang('invalid_address').': '.$address; + return false; + } + if ($kind != 'ReplyTo') { + if (!isset($this->all_recipients[strtolower($address)])) { + array_push($this->$kind, array($address, $name)); + $this->all_recipients[strtolower($address)] = true; + return true; + } + } else { + if (!array_key_exists(strtolower($address), $this->ReplyTo)) { + $this->ReplyTo[strtolower($address)] = array($address, $name); + return true; + } + } + return false; +} + +/** + * Set the From and FromName properties + * @param string $address + * @param string $name + * @return boolean + */ + public function SetFrom($address, $name = '', $auto = 1) { + $address = trim($address); + $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim + if (!self::ValidateAddress($address)) { + $this->SetError($this->Lang('invalid_address').': '. $address); + if ($this->exceptions) { + throw new phpmailerException($this->Lang('invalid_address').': '.$address); + } + echo $this->Lang('invalid_address').': '.$address; + return false; + } + $this->From = $address; + $this->FromName = $name; + if ($auto) { + if (empty($this->ReplyTo)) { + $this->AddAnAddress('ReplyTo', $address, $name); + } + if (empty($this->Sender)) { + $this->Sender = $address; + } + } + return true; + } + + /** + * Check that a string looks roughly like an email address should + * Static so it can be used without instantiation + * Tries to use PHP built-in validator in the filter extension (from PHP 5.2), falls back to a reasonably competent regex validator + * Conforms approximately to RFC2822 + * @link http://www.hexillion.com/samples/#Regex Original pattern found here + * @param string $address The email address to check + * @return boolean + * @static + * @access public + */ + public static function ValidateAddress($address) { + if (function_exists('filter_var')) { //Introduced in PHP 5.2 + if(filter_var($address, FILTER_VALIDATE_EMAIL) === FALSE) { + return false; + } else { + return true; + } + } else { + return preg_match('/^(?:[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+\.)*[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+@(?:(?:(?:[a-zA-Z0-9_](?:[a-zA-Z0-9_\-](?!\.)){0,61}[a-zA-Z0-9_-]?\.)+[a-zA-Z0-9_](?:[a-zA-Z0-9_\-](?!$)){0,61}[a-zA-Z0-9_]?)|(?:\[(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\]))$/', $address); + } + } + + ///////////////////////////////////////////////// + // METHODS, MAIL SENDING + ///////////////////////////////////////////////// + + /** + * Creates message and assigns Mailer. If the message is + * not sent successfully then it returns false. Use the ErrorInfo + * variable to view description of the error. + * @return bool + */ + public function Send() { + try { + if(!$this->PreSend()) return false; + return $this->PostSend(); + } catch (phpmailerException $e) { + $this->SetError($e->getMessage()); + if ($this->exceptions) { + throw $e; + } + return false; + } + } + + protected function PreSend() { + try { + if ((count($this->to) + count($this->cc) + count($this->bcc)) < 1) { + throw new phpmailerException($this->Lang('provide_address'), self::STOP_CRITICAL); + } + + // Set whether the message is multipart/alternative + if(!empty($this->AltBody)) { + $this->ContentType = 'multipart/alternative'; + } + + $this->error_count = 0; // reset errors + $this->SetMessageType(); + //Refuse to send an empty message + if (empty($this->Body)) { + throw new phpmailerException($this->Lang('empty_message'), self::STOP_CRITICAL); + } + + $this->MIMEHeader = $this->CreateHeader(); + $this->MIMEBody = $this->CreateBody(); + + + // digitally sign with DKIM if enabled + if ($this->DKIM_domain && $this->DKIM_private) { + $header_dkim = $this->DKIM_Add($this->MIMEHeader, $this->EncodeHeader($this->SecureHeader($this->Subject)), $this->MIMEBody); + $this->MIMEHeader = str_replace("\r\n", "\n", $header_dkim) . $this->MIMEHeader; + } + + return true; + } catch (phpmailerException $e) { + $this->SetError($e->getMessage()); + if ($this->exceptions) { + throw $e; + } + return false; + } + } + + protected function PostSend() { + try { + // Choose the mailer and send through it + switch($this->Mailer) { + case 'sendmail': + return $this->SendmailSend($this->MIMEHeader, $this->MIMEBody); + case 'smtp': + return $this->SmtpSend($this->MIMEHeader, $this->MIMEBody); + default: + return $this->MailSend($this->MIMEHeader, $this->MIMEBody); + } + + } catch (phpmailerException $e) { + $this->SetError($e->getMessage()); + if ($this->exceptions) { + throw $e; + } + echo $e->getMessage()."\n"; + return false; + } + } + + /** + * Sends mail using the $Sendmail program. + * @param string $header The message headers + * @param string $body The message body + * @access protected + * @return bool + */ + protected function SendmailSend($header, $body) { + if ($this->Sender != '') { + $sendmail = sprintf("%s -oi -f %s -t", escapeshellcmd($this->Sendmail), escapeshellarg($this->Sender)); + } else { + $sendmail = sprintf("%s -oi -t", escapeshellcmd($this->Sendmail)); + } + if ($this->SingleTo === true) { + foreach ($this->SingleToArray as $key => $val) { + if(!@$mail = popen($sendmail, 'w')) { + throw new phpmailerException($this->Lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + fputs($mail, "To: " . $val . "\n"); + fputs($mail, $header); + fputs($mail, $body); + $result = pclose($mail); + // implement call back function if it exists + $isSent = ($result == 0) ? 1 : 0; + $this->doCallback($isSent, $val, $this->cc, $this->bcc, $this->Subject, $body); + if($result != 0) { + throw new phpmailerException($this->Lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + } + } else { + if(!@$mail = popen($sendmail, 'w')) { + throw new phpmailerException($this->Lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + fputs($mail, $header); + fputs($mail, $body); + $result = pclose($mail); + // implement call back function if it exists + $isSent = ($result == 0) ? 1 : 0; + $this->doCallback($isSent, $this->to, $this->cc, $this->bcc, $this->Subject, $body); + if($result != 0) { + throw new phpmailerException($this->Lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + } + return true; + } + + /** + * Sends mail using the PHP mail() function. + * @param string $header The message headers + * @param string $body The message body + * @access protected + * @return bool + */ + protected function MailSend($header, $body) { + $toArr = array(); + foreach($this->to as $t) { + $toArr[] = $this->AddrFormat($t); + } + $to = implode(', ', $toArr); + + if (empty($this->Sender)) { + $params = "-oi -f %s"; + } else { + $params = sprintf("-oi -f %s", $this->Sender); + } + if ($this->Sender != '' and !ini_get('safe_mode')) { + $old_from = ini_get('sendmail_from'); + ini_set('sendmail_from', $this->Sender); + if ($this->SingleTo === true && count($toArr) > 1) { + foreach ($toArr as $key => $val) { + $rt = @mail($val, $this->EncodeHeader($this->SecureHeader($this->Subject)), $body, $header, $params); + // implement call back function if it exists + $isSent = ($rt == 1) ? 1 : 0; + $this->doCallback($isSent, $val, $this->cc, $this->bcc, $this->Subject, $body); + } + } else { + $rt = @mail($to, $this->EncodeHeader($this->SecureHeader($this->Subject)), $body, $header, $params); + // implement call back function if it exists + $isSent = ($rt == 1) ? 1 : 0; + $this->doCallback($isSent, $to, $this->cc, $this->bcc, $this->Subject, $body); + } + } else { + if ($this->SingleTo === true && count($toArr) > 1) { + foreach ($toArr as $key => $val) { + $rt = @mail($val, $this->EncodeHeader($this->SecureHeader($this->Subject)), $body, $header, $params); + // implement call back function if it exists + $isSent = ($rt == 1) ? 1 : 0; + $this->doCallback($isSent, $val, $this->cc, $this->bcc, $this->Subject, $body); + } + } else { + $rt = @mail($to, $this->EncodeHeader($this->SecureHeader($this->Subject)), $body, $header); + // implement call back function if it exists + $isSent = ($rt == 1) ? 1 : 0; + $this->doCallback($isSent, $to, $this->cc, $this->bcc, $this->Subject, $body); + } + } + if (isset($old_from)) { + ini_set('sendmail_from', $old_from); + } + if(!$rt) { + throw new phpmailerException($this->Lang('instantiate'), self::STOP_CRITICAL); + } + return true; + } + + /** + * Sends mail via SMTP using PhpSMTP + * Returns false if there is a bad MAIL FROM, RCPT, or DATA input. + * @param string $header The message headers + * @param string $body The message body + * @uses SMTP + * @access protected + * @return bool + */ + protected function SmtpSend($header, $body) { + require_once $this->PluginDir . 'class.smtp.php'; + $bad_rcpt = array(); + + if(!$this->SmtpConnect()) { + throw new phpmailerException($this->Lang('smtp_connect_failed'), self::STOP_CRITICAL); + } + $smtp_from = ($this->Sender == '') ? $this->From : $this->Sender; + if(!$this->smtp->Mail($smtp_from)) { + throw new phpmailerException($this->Lang('from_failed') . $smtp_from, self::STOP_CRITICAL); + } + + // Attempt to send attach all recipients + foreach($this->to as $to) { + if (!$this->smtp->Recipient($to[0])) { + $bad_rcpt[] = $to[0]; + // implement call back function if it exists + $isSent = 0; + $this->doCallback($isSent, $to[0], '', '', $this->Subject, $body); + } else { + // implement call back function if it exists + $isSent = 1; + $this->doCallback($isSent, $to[0], '', '', $this->Subject, $body); + } + } + foreach($this->cc as $cc) { + if (!$this->smtp->Recipient($cc[0])) { + $bad_rcpt[] = $cc[0]; + // implement call back function if it exists + $isSent = 0; + $this->doCallback($isSent, '', $cc[0], '', $this->Subject, $body); + } else { + // implement call back function if it exists + $isSent = 1; + $this->doCallback($isSent, '', $cc[0], '', $this->Subject, $body); + } + } + foreach($this->bcc as $bcc) { + if (!$this->smtp->Recipient($bcc[0])) { + $bad_rcpt[] = $bcc[0]; + // implement call back function if it exists + $isSent = 0; + $this->doCallback($isSent, '', '', $bcc[0], $this->Subject, $body); + } else { + // implement call back function if it exists + $isSent = 1; + $this->doCallback($isSent, '', '', $bcc[0], $this->Subject, $body); + } + } + + + if (count($bad_rcpt) > 0 ) { //Create error message for any bad addresses + $badaddresses = implode(', ', $bad_rcpt); + throw new phpmailerException($this->Lang('recipients_failed') . $badaddresses); + } + if(!$this->smtp->Data($header . $body)) { + throw new phpmailerException($this->Lang('data_not_accepted'), self::STOP_CRITICAL); + } + if($this->SMTPKeepAlive == true) { + $this->smtp->Reset(); + } + return true; + } + + /** + * Initiates a connection to an SMTP server. + * Returns false if the operation failed. + * @uses SMTP + * @access public + * @return bool + */ + public function SmtpConnect() { + if(is_null($this->smtp)) { + $this->smtp = new SMTP(); + } + + $this->smtp->do_debug = $this->SMTPDebug; + $hosts = explode(';', $this->Host); + $index = 0; + $connection = $this->smtp->Connected(); + + // Retry while there is no connection + try { + while($index < count($hosts) && !$connection) { + $hostinfo = array(); + if (preg_match('/^(.+):([0-9]+)$/', $hosts[$index], $hostinfo)) { + $host = $hostinfo[1]; + $port = $hostinfo[2]; + } else { + $host = $hosts[$index]; + $port = $this->Port; + } + + $tls = ($this->SMTPSecure == 'tls'); + $ssl = ($this->SMTPSecure == 'ssl'); + + if ($this->smtp->Connect(($ssl ? 'ssl://':'').$host, $port, $this->Timeout)) { + + $hello = ($this->Helo != '' ? $this->Helo : $this->ServerHostname()); + $this->smtp->Hello($hello); + + if ($tls) { + if (!$this->smtp->StartTLS()) { + throw new phpmailerException($this->Lang('tls')); + } + + //We must resend HELO after tls negotiation + $this->smtp->Hello($hello); + } + + $connection = true; + if ($this->SMTPAuth) { + if (!$this->smtp->Authenticate($this->Username, $this->Password)) { + throw new phpmailerException($this->Lang('authenticate')); + } + } + } + $index++; + if (!$connection) { + throw new phpmailerException($this->Lang('connect_host')); + } + } + } catch (phpmailerException $e) { + $this->smtp->Reset(); + throw $e; + } + return true; + } + + /** + * Closes the active SMTP session if one exists. + * @return void + */ + public function SmtpClose() { + if(!is_null($this->smtp)) { + if($this->smtp->Connected()) { + $this->smtp->Quit(); + $this->smtp->Close(); + } + } + } + + /** + * Sets the language for all class error messages. + * Returns false if it cannot load the language file. The default language is English. + * @param string $langcode ISO 639-1 2-character language code (e.g. Portuguese: "br") + * @param string $lang_path Path to the language file directory + * @access public + */ + function SetLanguage($langcode = 'en', $lang_path = 'language/') { + //Define full set of translatable strings + $PHPMAILER_LANG = array( + 'provide_address' => 'You must provide at least one recipient email address.', + 'mailer_not_supported' => ' mailer is not supported.', + 'execute' => 'Could not execute: ', + 'instantiate' => 'Could not instantiate mail function.', + 'authenticate' => 'SMTP Error: Could not authenticate.', + 'from_failed' => 'The following From address failed: ', + 'recipients_failed' => 'SMTP Error: The following recipients failed: ', + 'data_not_accepted' => 'SMTP Error: Data not accepted.', + 'connect_host' => 'SMTP Error: Could not connect to SMTP host.', + 'file_access' => 'Could not access file: ', + 'file_open' => 'File Error: Could not open file: ', + 'encoding' => 'Unknown encoding: ', + 'signing' => 'Signing Error: ', + 'smtp_error' => 'SMTP server error: ', + 'empty_message' => 'Message body empty', + 'invalid_address' => 'Invalid address', + 'variable_set' => 'Cannot set or reset variable: ' + ); + //Overwrite language-specific strings. This way we'll never have missing translations - no more "language string failed to load"! + $l = true; + if ($langcode != 'en') { //There is no English translation file + $l = @include $lang_path.'phpmailer.lang-'.$langcode.'.php'; + } + $this->language = $PHPMAILER_LANG; + return ($l == true); //Returns false if language not found + } + + /** + * Return the current array of language strings + * @return array + */ + public function GetTranslations() { + return $this->language; + } + + ///////////////////////////////////////////////// + // METHODS, MESSAGE CREATION + ///////////////////////////////////////////////// + + /** + * Creates recipient headers. + * @access public + * @return string + */ + public function AddrAppend($type, $addr) { + $addr_str = $type . ': '; + $addresses = array(); + foreach ($addr as $a) { + $addresses[] = $this->AddrFormat($a); + } + $addr_str .= implode(', ', $addresses); + $addr_str .= $this->LE; + + return $addr_str; + } + + /** + * Formats an address correctly. + * @access public + * @return string + */ + public function AddrFormat($addr) { + if (empty($addr[1])) { + return $this->SecureHeader($addr[0]); + } else { + return $this->EncodeHeader($this->SecureHeader($addr[1]), 'phrase') . " <" . $this->SecureHeader($addr[0]) . ">"; + } + } + + /** + * Wraps message for use with mailers that do not + * automatically perform wrapping and for quoted-printable. + * Original written by philippe. + * @param string $message The message to wrap + * @param integer $length The line length to wrap to + * @param boolean $qp_mode Whether to run in Quoted-Printable mode + * @access public + * @return string + */ + public function WrapText($message, $length, $qp_mode = false) { + $soft_break = ($qp_mode) ? sprintf(" =%s", $this->LE) : $this->LE; + // If utf-8 encoding is used, we will need to make sure we don't + // split multibyte characters when we wrap + $is_utf8 = (strtolower($this->CharSet) == "utf-8"); + + $message = $this->FixEOL($message); + if (substr($message, -1) == $this->LE) { + $message = substr($message, 0, -1); + } + + $line = explode($this->LE, $message); + $message = ''; + for ($i = 0 ;$i < count($line); $i++) { + $line_part = explode(' ', $line[$i]); + $buf = ''; + for ($e = 0; $e $length)) { + $space_left = $length - strlen($buf) - 1; + if ($e != 0) { + if ($space_left > 20) { + $len = $space_left; + if ($is_utf8) { + $len = $this->UTF8CharBoundary($word, $len); + } elseif (substr($word, $len - 1, 1) == "=") { + $len--; + } elseif (substr($word, $len - 2, 1) == "=") { + $len -= 2; + } + $part = substr($word, 0, $len); + $word = substr($word, $len); + $buf .= ' ' . $part; + $message .= $buf . sprintf("=%s", $this->LE); + } else { + $message .= $buf . $soft_break; + } + $buf = ''; + } + while (strlen($word) > 0) { + $len = $length; + if ($is_utf8) { + $len = $this->UTF8CharBoundary($word, $len); + } elseif (substr($word, $len - 1, 1) == "=") { + $len--; + } elseif (substr($word, $len - 2, 1) == "=") { + $len -= 2; + } + $part = substr($word, 0, $len); + $word = substr($word, $len); + + if (strlen($word) > 0) { + $message .= $part . sprintf("=%s", $this->LE); + } else { + $buf = $part; + } + } + } else { + $buf_o = $buf; + $buf .= ($e == 0) ? $word : (' ' . $word); + + if (strlen($buf) > $length and $buf_o != '') { + $message .= $buf_o . $soft_break; + $buf = $word; + } + } + } + $message .= $buf . $this->LE; + } + + return $message; + } + + /** + * Finds last character boundary prior to maxLength in a utf-8 + * quoted (printable) encoded string. + * Original written by Colin Brown. + * @access public + * @param string $encodedText utf-8 QP text + * @param int $maxLength find last character boundary prior to this length + * @return int + */ + public function UTF8CharBoundary($encodedText, $maxLength) { + $foundSplitPos = false; + $lookBack = 3; + while (!$foundSplitPos) { + $lastChunk = substr($encodedText, $maxLength - $lookBack, $lookBack); + $encodedCharPos = strpos($lastChunk, "="); + if ($encodedCharPos !== false) { + // Found start of encoded character byte within $lookBack block. + // Check the encoded byte value (the 2 chars after the '=') + $hex = substr($encodedText, $maxLength - $lookBack + $encodedCharPos + 1, 2); + $dec = hexdec($hex); + if ($dec < 128) { // Single byte character. + // If the encoded char was found at pos 0, it will fit + // otherwise reduce maxLength to start of the encoded char + $maxLength = ($encodedCharPos == 0) ? $maxLength : + $maxLength - ($lookBack - $encodedCharPos); + $foundSplitPos = true; + } elseif ($dec >= 192) { // First byte of a multi byte character + // Reduce maxLength to split at start of character + $maxLength = $maxLength - ($lookBack - $encodedCharPos); + $foundSplitPos = true; + } elseif ($dec < 192) { // Middle byte of a multi byte character, look further back + $lookBack += 3; + } + } else { + // No encoded character found + $foundSplitPos = true; + } + } + return $maxLength; + } + + + /** + * Set the body wrapping. + * @access public + * @return void + */ + public function SetWordWrap() { + if($this->WordWrap < 1) { + return; + } + + switch($this->message_type) { + case 'alt': + case 'alt_inline': + case 'alt_attach': + case 'alt_inline_attach': + $this->AltBody = $this->WrapText($this->AltBody, $this->WordWrap); + break; + default: + $this->Body = $this->WrapText($this->Body, $this->WordWrap); + break; + } + } + + /** + * Assembles message header. + * @access public + * @return string The assembled header + */ + public function CreateHeader() { + $result = ''; + + // Set the boundaries + $uniq_id = md5(uniqid(time())); + $this->boundary[1] = 'b1_' . $uniq_id; + $this->boundary[2] = 'b2_' . $uniq_id; + $this->boundary[3] = 'b3_' . $uniq_id; + + $result .= $this->HeaderLine('Date', self::RFCDate()); + if($this->Sender == '') { + $result .= $this->HeaderLine('Return-Path', trim($this->From)); + } else { + $result .= $this->HeaderLine('Return-Path', trim($this->Sender)); + } + + // To be created automatically by mail() + if($this->Mailer != 'mail') { + if ($this->SingleTo === true) { + foreach($this->to as $t) { + $this->SingleToArray[] = $this->AddrFormat($t); + } + } else { + if(count($this->to) > 0) { + $result .= $this->AddrAppend('To', $this->to); + } elseif (count($this->cc) == 0) { + $result .= $this->HeaderLine('To', 'undisclosed-recipients:;'); + } + } + } + + $from = array(); + $from[0][0] = trim($this->From); + $from[0][1] = $this->FromName; + $result .= $this->AddrAppend('From', $from); + + // sendmail and mail() extract Cc from the header before sending + if(count($this->cc) > 0) { + $result .= $this->AddrAppend('Cc', $this->cc); + } + + // sendmail and mail() extract Bcc from the header before sending + if((($this->Mailer == 'sendmail') || ($this->Mailer == 'mail')) && (count($this->bcc) > 0)) { + $result .= $this->AddrAppend('Bcc', $this->bcc); + } + + if(count($this->ReplyTo) > 0) { + $result .= $this->AddrAppend('Reply-to', $this->ReplyTo); + } + + // mail() sets the subject itself + if($this->Mailer != 'mail') { + $result .= $this->HeaderLine('Subject', $this->EncodeHeader($this->SecureHeader($this->Subject))); + } + + if($this->MessageID != '') { + $result .= $this->HeaderLine('Message-ID', $this->MessageID); + } else { + $result .= sprintf("Message-ID: <%s@%s>%s", $uniq_id, $this->ServerHostname(), $this->LE); + } + $result .= $this->HeaderLine('X-Priority', $this->Priority); + if($this->XMailer) { + $result .= $this->HeaderLine('X-Mailer', $this->XMailer); + } else { + $result .= $this->HeaderLine('X-Mailer', 'PHPMailer '.$this->Version.' (http://code.google.com/a/apache-extras.org/p/phpmailer/)'); + } + + if($this->ConfirmReadingTo != '') { + $result .= $this->HeaderLine('Disposition-Notification-To', '<' . trim($this->ConfirmReadingTo) . '>'); + } + + // Add custom headers + for($index = 0; $index < count($this->CustomHeader); $index++) { + $result .= $this->HeaderLine(trim($this->CustomHeader[$index][0]), $this->EncodeHeader(trim($this->CustomHeader[$index][1]))); + } + if (!$this->sign_key_file) { + $result .= $this->HeaderLine('MIME-Version', '1.0'); + $result .= $this->GetMailMIME(); + } + + return $result; + } + + /** + * Returns the message MIME. + * @access public + * @return string + */ + public function GetMailMIME() { + $result = ''; + switch($this->message_type) { + case 'plain': + $result .= $this->HeaderLine('Content-Transfer-Encoding', $this->Encoding); + $result .= $this->TextLine('Content-Type: '.$this->ContentType.'; charset="'.$this->CharSet.'"'); + break; + case 'inline': + $result .= $this->HeaderLine('Content-Type', 'multipart/related;'); + $result .= $this->TextLine("\tboundary=\"" . $this->boundary[1] . '"'); + break; + case 'attach': + case 'inline_attach': + case 'alt_attach': + case 'alt_inline_attach': + $result .= $this->HeaderLine('Content-Type', 'multipart/mixed;'); + $result .= $this->TextLine("\tboundary=\"" . $this->boundary[1] . '"'); + break; + case 'alt': + case 'alt_inline': + $result .= $this->HeaderLine('Content-Type', 'multipart/alternative;'); + $result .= $this->TextLine("\tboundary=\"" . $this->boundary[1] . '"'); + break; + } + + if($this->Mailer != 'mail') { + $result .= $this->LE.$this->LE; + } + + return $result; + } + + /** + * Assembles the message body. Returns an empty string on failure. + * @access public + * @return string The assembled message body + */ + public function CreateBody() { + $body = ''; + + if ($this->sign_key_file) { + $body .= $this->GetMailMIME(); + } + + $this->SetWordWrap(); + + switch($this->message_type) { + case 'plain': + $body .= $this->EncodeString($this->Body, $this->Encoding); + break; + case 'inline': + $body .= $this->GetBoundary($this->boundary[1], '', '', ''); + $body .= $this->EncodeString($this->Body, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->AttachAll("inline", $this->boundary[1]); + break; + case 'attach': + $body .= $this->GetBoundary($this->boundary[1], '', '', ''); + $body .= $this->EncodeString($this->Body, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->AttachAll("attachment", $this->boundary[1]); + break; + case 'inline_attach': + $body .= $this->TextLine("--" . $this->boundary[1]); + $body .= $this->HeaderLine('Content-Type', 'multipart/related;'); + $body .= $this->TextLine("\tboundary=\"" . $this->boundary[2] . '"'); + $body .= $this->LE; + $body .= $this->GetBoundary($this->boundary[2], '', '', ''); + $body .= $this->EncodeString($this->Body, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->AttachAll("inline", $this->boundary[2]); + $body .= $this->LE; + $body .= $this->AttachAll("attachment", $this->boundary[1]); + break; + case 'alt': + $body .= $this->GetBoundary($this->boundary[1], '', 'text/plain', ''); + $body .= $this->EncodeString($this->AltBody, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->GetBoundary($this->boundary[1], '', 'text/html', ''); + $body .= $this->EncodeString($this->Body, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->EndBoundary($this->boundary[1]); + break; + case 'alt_inline': + $body .= $this->GetBoundary($this->boundary[1], '', 'text/plain', ''); + $body .= $this->EncodeString($this->AltBody, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->TextLine("--" . $this->boundary[1]); + $body .= $this->HeaderLine('Content-Type', 'multipart/related;'); + $body .= $this->TextLine("\tboundary=\"" . $this->boundary[2] . '"'); + $body .= $this->LE; + $body .= $this->GetBoundary($this->boundary[2], '', 'text/html', ''); + $body .= $this->EncodeString($this->Body, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->AttachAll("inline", $this->boundary[2]); + $body .= $this->LE; + $body .= $this->EndBoundary($this->boundary[1]); + break; + case 'alt_attach': + $body .= $this->TextLine("--" . $this->boundary[1]); + $body .= $this->HeaderLine('Content-Type', 'multipart/alternative;'); + $body .= $this->TextLine("\tboundary=\"" . $this->boundary[2] . '"'); + $body .= $this->LE; + $body .= $this->GetBoundary($this->boundary[2], '', 'text/plain', ''); + $body .= $this->EncodeString($this->AltBody, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->GetBoundary($this->boundary[2], '', 'text/html', ''); + $body .= $this->EncodeString($this->Body, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->EndBoundary($this->boundary[2]); + $body .= $this->LE; + $body .= $this->AttachAll("attachment", $this->boundary[1]); + break; + case 'alt_inline_attach': + $body .= $this->TextLine("--" . $this->boundary[1]); + $body .= $this->HeaderLine('Content-Type', 'multipart/alternative;'); + $body .= $this->TextLine("\tboundary=\"" . $this->boundary[2] . '"'); + $body .= $this->LE; + $body .= $this->GetBoundary($this->boundary[2], '', 'text/plain', ''); + $body .= $this->EncodeString($this->AltBody, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->TextLine("--" . $this->boundary[2]); + $body .= $this->HeaderLine('Content-Type', 'multipart/related;'); + $body .= $this->TextLine("\tboundary=\"" . $this->boundary[3] . '"'); + $body .= $this->LE; + $body .= $this->GetBoundary($this->boundary[3], '', 'text/html', ''); + $body .= $this->EncodeString($this->Body, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->AttachAll("inline", $this->boundary[3]); + $body .= $this->LE; + $body .= $this->EndBoundary($this->boundary[2]); + $body .= $this->LE; + $body .= $this->AttachAll("attachment", $this->boundary[1]); + break; + } + + if ($this->IsError()) { + $body = ''; + } elseif ($this->sign_key_file) { + try { + $file = tempnam('', 'mail'); + file_put_contents($file, $body); //TODO check this worked + $signed = tempnam("", "signed"); + if (@openssl_pkcs7_sign($file, $signed, "file://".$this->sign_cert_file, array("file://".$this->sign_key_file, $this->sign_key_pass), NULL)) { + @unlink($file); + @unlink($signed); + $body = file_get_contents($signed); + } else { + @unlink($file); + @unlink($signed); + throw new phpmailerException($this->Lang("signing").openssl_error_string()); + } + } catch (phpmailerException $e) { + $body = ''; + if ($this->exceptions) { + throw $e; + } + } + } + + return $body; + } + + /** + * Returns the start of a message boundary. + * @access protected + * @return string + */ + protected function GetBoundary($boundary, $charSet, $contentType, $encoding) { + $result = ''; + if($charSet == '') { + $charSet = $this->CharSet; + } + if($contentType == '') { + $contentType = $this->ContentType; + } + if($encoding == '') { + $encoding = $this->Encoding; + } + $result .= $this->TextLine('--' . $boundary); + $result .= sprintf("Content-Type: %s; charset=\"%s\"", $contentType, $charSet); + $result .= $this->LE; + $result .= $this->HeaderLine('Content-Transfer-Encoding', $encoding); + $result .= $this->LE; + + return $result; + } + + /** + * Returns the end of a message boundary. + * @access protected + * @return string + */ + protected function EndBoundary($boundary) { + return $this->LE . '--' . $boundary . '--' . $this->LE; + } + + /** + * Sets the message type. + * @access protected + * @return void + */ + protected function SetMessageType() { + $this->message_type = array(); + if($this->AlternativeExists()) $this->message_type[] = "alt"; + if($this->InlineImageExists()) $this->message_type[] = "inline"; + if($this->AttachmentExists()) $this->message_type[] = "attach"; + $this->message_type = implode("_", $this->message_type); + if($this->message_type == "") $this->message_type = "plain"; + } + + /** + * Returns a formatted header line. + * @access public + * @return string + */ + public function HeaderLine($name, $value) { + return $name . ': ' . $value . $this->LE; + } + + /** + * Returns a formatted mail line. + * @access public + * @return string + */ + public function TextLine($value) { + return $value . $this->LE; + } + + ///////////////////////////////////////////////// + // CLASS METHODS, ATTACHMENTS + ///////////////////////////////////////////////// + + /** + * Adds an attachment from a path on the filesystem. + * Returns false if the file could not be found + * or accessed. + * @param string $path Path to the attachment. + * @param string $name Overrides the attachment name. + * @param string $encoding File encoding (see $Encoding). + * @param string $type File extension (MIME) type. + * @return bool + */ + public function AddAttachment($path, $name = '', $encoding = 'base64', $type = 'application/octet-stream') { + try { + if ( !@is_file($path) ) { + throw new phpmailerException($this->Lang('file_access') . $path, self::STOP_CONTINUE); + } + $filename = basename($path); + if ( $name == '' ) { + $name = $filename; + } + + $this->attachment[] = array( + 0 => $path, + 1 => $filename, + 2 => $name, + 3 => $encoding, + 4 => $type, + 5 => false, // isStringAttachment + 6 => 'attachment', + 7 => 0 + ); + + } catch (phpmailerException $e) { + $this->SetError($e->getMessage()); + if ($this->exceptions) { + throw $e; + } + echo $e->getMessage()."\n"; + if ( $e->getCode() == self::STOP_CRITICAL ) { + return false; + } + } + return true; + } + + /** + * Return the current array of attachments + * @return array + */ + public function GetAttachments() { + return $this->attachment; + } + + /** + * Attaches all fs, string, and binary attachments to the message. + * Returns an empty string on failure. + * @access protected + * @return string + */ + protected function AttachAll($disposition_type, $boundary) { + // Return text of body + $mime = array(); + $cidUniq = array(); + $incl = array(); + + // Add all attachments + foreach ($this->attachment as $attachment) { + // CHECK IF IT IS A VALID DISPOSITION_FILTER + if($attachment[6] == $disposition_type) { + // Check for string attachment + $bString = $attachment[5]; + if ($bString) { + $string = $attachment[0]; + } else { + $path = $attachment[0]; + } + + $inclhash = md5(serialize($attachment)); + if (in_array($inclhash, $incl)) { continue; } + $incl[] = $inclhash; + $filename = $attachment[1]; + $name = $attachment[2]; + $encoding = $attachment[3]; + $type = $attachment[4]; + $disposition = $attachment[6]; + $cid = $attachment[7]; + if ( $disposition == 'inline' && isset($cidUniq[$cid]) ) { continue; } + $cidUniq[$cid] = true; + + $mime[] = sprintf("--%s%s", $boundary, $this->LE); + $mime[] = sprintf("Content-Type: %s; name=\"%s\"%s", $type, $this->EncodeHeader($this->SecureHeader($name)), $this->LE); + $mime[] = sprintf("Content-Transfer-Encoding: %s%s", $encoding, $this->LE); + + if($disposition == 'inline') { + $mime[] = sprintf("Content-ID: <%s>%s", $cid, $this->LE); + } + + $mime[] = sprintf("Content-Disposition: %s; filename=\"%s\"%s", $disposition, $this->EncodeHeader($this->SecureHeader($name)), $this->LE.$this->LE); + + // Encode as string attachment + if($bString) { + $mime[] = $this->EncodeString($string, $encoding); + if($this->IsError()) { + return ''; + } + $mime[] = $this->LE.$this->LE; + } else { + $mime[] = $this->EncodeFile($path, $encoding); + if($this->IsError()) { + return ''; + } + $mime[] = $this->LE.$this->LE; + } + } + } + + $mime[] = sprintf("--%s--%s", $boundary, $this->LE); + + return implode("", $mime); + } + + /** + * Encodes attachment in requested format. + * Returns an empty string on failure. + * @param string $path The full path to the file + * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable' + * @see EncodeFile() + * @access protected + * @return string + */ + protected function EncodeFile($path, $encoding = 'base64') { + try { + if (!is_readable($path)) { + throw new phpmailerException($this->Lang('file_open') . $path, self::STOP_CONTINUE); + } + if (function_exists('get_magic_quotes')) { + function get_magic_quotes() { + return false; + } + } + if (version_compare(PHP_VERSION, '5.3.0', '<')) { + $magic_quotes = get_magic_quotes_runtime(); + set_magic_quotes_runtime(0); + } + $file_buffer = file_get_contents($path); + $file_buffer = $this->EncodeString($file_buffer, $encoding); + if (version_compare(PHP_VERSION, '5.3.0', '<')) { + set_magic_quotes_runtime($magic_quotes); + } + return $file_buffer; + } catch (Exception $e) { + $this->SetError($e->getMessage()); + return ''; + } + } + + /** + * Encodes string to requested format. + * Returns an empty string on failure. + * @param string $str The text to encode + * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable' + * @access public + * @return string + */ + public function EncodeString($str, $encoding = 'base64') { + $encoded = ''; + switch(strtolower($encoding)) { + case 'base64': + $encoded = chunk_split(base64_encode($str), 76, $this->LE); + break; + case '7bit': + case '8bit': + $encoded = $this->FixEOL($str); + //Make sure it ends with a line break + if (substr($encoded, -(strlen($this->LE))) != $this->LE) + $encoded .= $this->LE; + break; + case 'binary': + $encoded = $str; + break; + case 'quoted-printable': + $encoded = $this->EncodeQP($str); + break; + default: + $this->SetError($this->Lang('encoding') . $encoding); + break; + } + return $encoded; + } + + /** + * Encode a header string to best (shortest) of Q, B, quoted or none. + * @access public + * @return string + */ + public function EncodeHeader($str, $position = 'text') { + $x = 0; + + switch (strtolower($position)) { + case 'phrase': + if (!preg_match('/[\200-\377]/', $str)) { + // Can't use addslashes as we don't know what value has magic_quotes_sybase + $encoded = addcslashes($str, "\0..\37\177\\\""); + if (($str == $encoded) && !preg_match('/[^A-Za-z0-9!#$%&\'*+\/=?^_`{|}~ -]/', $str)) { + return ($encoded); + } else { + return ("\"$encoded\""); + } + } + $x = preg_match_all('/[^\040\041\043-\133\135-\176]/', $str, $matches); + break; + case 'comment': + $x = preg_match_all('/[()"]/', $str, $matches); + // Fall-through + case 'text': + default: + $x += preg_match_all('/[\000-\010\013\014\016-\037\177-\377]/', $str, $matches); + break; + } + + if ($x == 0) { + return ($str); + } + + $maxlen = 75 - 7 - strlen($this->CharSet); + // Try to select the encoding which should produce the shortest output + if (strlen($str)/3 < $x) { + $encoding = 'B'; + if (function_exists('mb_strlen') && $this->HasMultiBytes($str)) { + // Use a custom function which correctly encodes and wraps long + // multibyte strings without breaking lines within a character + $encoded = $this->Base64EncodeWrapMB($str); + } else { + $encoded = base64_encode($str); + $maxlen -= $maxlen % 4; + $encoded = trim(chunk_split($encoded, $maxlen, "\n")); + } + } else { + $encoding = 'Q'; + $encoded = $this->EncodeQ($str, $position); + $encoded = $this->WrapText($encoded, $maxlen, true); + $encoded = str_replace('='.$this->LE, "\n", trim($encoded)); + } + + $encoded = preg_replace('/^(.*)$/m', " =?".$this->CharSet."?$encoding?\\1?=", $encoded); + $encoded = trim(str_replace("\n", $this->LE, $encoded)); + + return $encoded; + } + + /** + * Checks if a string contains multibyte characters. + * @access public + * @param string $str multi-byte text to wrap encode + * @return bool + */ + public function HasMultiBytes($str) { + if (function_exists('mb_strlen')) { + return (strlen($str) > mb_strlen($str, $this->CharSet)); + } else { // Assume no multibytes (we can't handle without mbstring functions anyway) + return false; + } + } + + /** + * Correctly encodes and wraps long multibyte strings for mail headers + * without breaking lines within a character. + * Adapted from a function by paravoid at http://uk.php.net/manual/en/function.mb-encode-mimeheader.php + * @access public + * @param string $str multi-byte text to wrap encode + * @return string + */ + public function Base64EncodeWrapMB($str) { + $start = "=?".$this->CharSet."?B?"; + $end = "?="; + $encoded = ""; + + $mb_length = mb_strlen($str, $this->CharSet); + // Each line must have length <= 75, including $start and $end + $length = 75 - strlen($start) - strlen($end); + // Average multi-byte ratio + $ratio = $mb_length / strlen($str); + // Base64 has a 4:3 ratio + $offset = $avgLength = floor($length * $ratio * .75); + + for ($i = 0; $i < $mb_length; $i += $offset) { + $lookBack = 0; + + do { + $offset = $avgLength - $lookBack; + $chunk = mb_substr($str, $i, $offset, $this->CharSet); + $chunk = base64_encode($chunk); + $lookBack++; + } + while (strlen($chunk) > $length); + + $encoded .= $chunk . $this->LE; + } + + // Chomp the last linefeed + $encoded = substr($encoded, 0, -strlen($this->LE)); + return $encoded; + } + + /** + * Encode string to quoted-printable. + * Only uses standard PHP, slow, but will always work + * @access public + * @param string $string the text to encode + * @param integer $line_max Number of chars allowed on a line before wrapping + * @return string + */ + public function EncodeQPphp( $input = '', $line_max = 76, $space_conv = false) { + $hex = array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'); + $lines = preg_split('/(?:\r\n|\r|\n)/', $input); + $eol = "\r\n"; + $escape = '='; + $output = ''; + while( list(, $line) = each($lines) ) { + $linlen = strlen($line); + $newline = ''; + for($i = 0; $i < $linlen; $i++) { + $c = substr( $line, $i, 1 ); + $dec = ord( $c ); + if ( ( $i == 0 ) && ( $dec == 46 ) ) { // convert first point in the line into =2E + $c = '=2E'; + } + if ( $dec == 32 ) { + if ( $i == ( $linlen - 1 ) ) { // convert space at eol only + $c = '=20'; + } else if ( $space_conv ) { + $c = '=20'; + } + } elseif ( ($dec == 61) || ($dec < 32 ) || ($dec > 126) ) { // always encode "\t", which is *not* required + $h2 = floor($dec/16); + $h1 = floor($dec%16); + $c = $escape.$hex[$h2].$hex[$h1]; + } + if ( (strlen($newline) + strlen($c)) >= $line_max ) { // CRLF is not counted + $output .= $newline.$escape.$eol; // soft line break; " =\r\n" is okay + $newline = ''; + // check if newline first character will be point or not + if ( $dec == 46 ) { + $c = '=2E'; + } + } + $newline .= $c; + } // end of for + $output .= $newline.$eol; + } // end of while + return $output; + } + + /** + * Encode string to RFC2045 (6.7) quoted-printable format + * Uses a PHP5 stream filter to do the encoding about 64x faster than the old version + * Also results in same content as you started with after decoding + * @see EncodeQPphp() + * @access public + * @param string $string the text to encode + * @param integer $line_max Number of chars allowed on a line before wrapping + * @param boolean $space_conv Dummy param for compatibility with existing EncodeQP function + * @return string + * @author Marcus Bointon + */ + public function EncodeQP($string, $line_max = 76, $space_conv = false) { + if (function_exists('quoted_printable_encode')) { //Use native function if it's available (>= PHP5.3) + return quoted_printable_encode($string); + } + $filters = stream_get_filters(); + if (!in_array('convert.*', $filters)) { //Got convert stream filter? + return $this->EncodeQPphp($string, $line_max, $space_conv); //Fall back to old implementation + } + $fp = fopen('php://temp/', 'r+'); + $string = preg_replace('/\r\n?/', $this->LE, $string); //Normalise line breaks + $params = array('line-length' => $line_max, 'line-break-chars' => $this->LE); + $s = stream_filter_append($fp, 'convert.quoted-printable-encode', STREAM_FILTER_READ, $params); + fputs($fp, $string); + rewind($fp); + $out = stream_get_contents($fp); + stream_filter_remove($s); + $out = preg_replace('/^\./m', '=2E', $out); //Encode . if it is first char on a line, workaround for bug in Exchange + fclose($fp); + return $out; + } + + /** + * Encode string to q encoding. + * @link http://tools.ietf.org/html/rfc2047 + * @param string $str the text to encode + * @param string $position Where the text is going to be used, see the RFC for what that means + * @access public + * @return string + */ + public function EncodeQ($str, $position = 'text') { + // There should not be any EOL in the string + $encoded = preg_replace('/[\r\n]*/', '', $str); + + switch (strtolower($position)) { + case 'phrase': + $encoded = preg_replace("/([^A-Za-z0-9!*+\/ -])/e", "'='.sprintf('%02X', ord('\\1'))", $encoded); + break; + case 'comment': + $encoded = preg_replace("/([\(\)\"])/e", "'='.sprintf('%02X', ord('\\1'))", $encoded); + case 'text': + default: + // Replace every high ascii, control =, ? and _ characters + //TODO using /e (equivalent to eval()) is probably not a good idea + $encoded = preg_replace('/([\000-\011\013\014\016-\037\075\077\137\177-\377])/e', + "'='.sprintf('%02X', ord(stripslashes('\\1')))", $encoded); + break; + } + + // Replace every spaces to _ (more readable than =20) + $encoded = str_replace(' ', '_', $encoded); + + return $encoded; + } + + /** + * Adds a string or binary attachment (non-filesystem) to the list. + * This method can be used to attach ascii or binary data, + * such as a BLOB record from a database. + * @param string $string String attachment data. + * @param string $filename Name of the attachment. + * @param string $encoding File encoding (see $Encoding). + * @param string $type File extension (MIME) type. + * @return void + */ + public function AddStringAttachment($string, $filename, $encoding = 'base64', $type = 'application/octet-stream') { + // Append to $attachment array + $this->attachment[] = array( + 0 => $string, + 1 => $filename, + 2 => basename($filename), + 3 => $encoding, + 4 => $type, + 5 => true, // isStringAttachment + 6 => 'attachment', + 7 => 0 + ); + } + + /** + * Adds an embedded attachment. This can include images, sounds, and + * just about any other document. Make sure to set the $type to an + * image type. For JPEG images use "image/jpeg" and for GIF images + * use "image/gif". + * @param string $path Path to the attachment. + * @param string $cid Content ID of the attachment. Use this to identify + * the Id for accessing the image in an HTML form. + * @param string $name Overrides the attachment name. + * @param string $encoding File encoding (see $Encoding). + * @param string $type File extension (MIME) type. + * @return bool + */ + public function AddEmbeddedImage($path, $cid, $name = '', $encoding = 'base64', $type = 'application/octet-stream') { + + if ( !@is_file($path) ) { + $this->SetError($this->Lang('file_access') . $path); + return false; + } + + $filename = basename($path); + if ( $name == '' ) { + $name = $filename; + } + + // Append to $attachment array + $this->attachment[] = array( + 0 => $path, + 1 => $filename, + 2 => $name, + 3 => $encoding, + 4 => $type, + 5 => false, // isStringAttachment + 6 => 'inline', + 7 => $cid + ); + + return true; + } + + public function AddStringEmbeddedImage($string, $cid, $filename = '', $encoding = 'base64', $type = 'application/octet-stream') { + // Append to $attachment array + $this->attachment[] = array( + 0 => $string, + 1 => $filename, + 2 => basename($filename), + 3 => $encoding, + 4 => $type, + 5 => true, // isStringAttachment + 6 => 'inline', + 7 => $cid + ); + } + + /** + * Returns true if an inline attachment is present. + * @access public + * @return bool + */ + public function InlineImageExists() { + foreach($this->attachment as $attachment) { + if ($attachment[6] == 'inline') { + return true; + } + } + return false; + } + + public function AttachmentExists() { + foreach($this->attachment as $attachment) { + if ($attachment[6] == 'attachment') { + return true; + } + } + return false; + } + + public function AlternativeExists() { + return strlen($this->AltBody)>0; + } + + ///////////////////////////////////////////////// + // CLASS METHODS, MESSAGE RESET + ///////////////////////////////////////////////// + + /** + * Clears all recipients assigned in the TO array. Returns void. + * @return void + */ + public function ClearAddresses() { + foreach($this->to as $to) { + unset($this->all_recipients[strtolower($to[0])]); + } + $this->to = array(); + } + + /** + * Clears all recipients assigned in the CC array. Returns void. + * @return void + */ + public function ClearCCs() { + foreach($this->cc as $cc) { + unset($this->all_recipients[strtolower($cc[0])]); + } + $this->cc = array(); + } + + /** + * Clears all recipients assigned in the BCC array. Returns void. + * @return void + */ + public function ClearBCCs() { + foreach($this->bcc as $bcc) { + unset($this->all_recipients[strtolower($bcc[0])]); + } + $this->bcc = array(); + } + + /** + * Clears all recipients assigned in the ReplyTo array. Returns void. + * @return void + */ + public function ClearReplyTos() { + $this->ReplyTo = array(); + } + + /** + * Clears all recipients assigned in the TO, CC and BCC + * array. Returns void. + * @return void + */ + public function ClearAllRecipients() { + $this->to = array(); + $this->cc = array(); + $this->bcc = array(); + $this->all_recipients = array(); + } + + /** + * Clears all previously set filesystem, string, and binary + * attachments. Returns void. + * @return void + */ + public function ClearAttachments() { + $this->attachment = array(); + } + + /** + * Clears all custom headers. Returns void. + * @return void + */ + public function ClearCustomHeaders() { + $this->CustomHeader = array(); + } + + ///////////////////////////////////////////////// + // CLASS METHODS, MISCELLANEOUS + ///////////////////////////////////////////////// + + /** + * Adds the error message to the error container. + * @access protected + * @return void + */ + protected function SetError($msg) { + $this->error_count++; + if ($this->Mailer == 'smtp' and !is_null($this->smtp)) { + $lasterror = $this->smtp->getError(); + if (!empty($lasterror) and array_key_exists('smtp_msg', $lasterror)) { + $msg .= '

' . $this->Lang('smtp_error') . $lasterror['smtp_msg'] . "

\n"; + } + } + $this->ErrorInfo = $msg; + } + + /** + * Returns the proper RFC 822 formatted date. + * @access public + * @return string + * @static + */ + public static function RFCDate() { + $tz = date('Z'); + $tzs = ($tz < 0) ? '-' : '+'; + $tz = abs($tz); + $tz = (int)($tz/3600)*100 + ($tz%3600)/60; + $result = sprintf("%s %s%04d", date('D, j M Y H:i:s'), $tzs, $tz); + + return $result; + } + + /** + * Returns the server hostname or 'localhost.localdomain' if unknown. + * @access protected + * @return string + */ + protected function ServerHostname() { + if (!empty($this->Hostname)) { + $result = $this->Hostname; + } elseif (isset($_SERVER['SERVER_NAME'])) { + $result = $_SERVER['SERVER_NAME']; + } else { + $result = 'localhost.localdomain'; + } + + return $result; + } + + /** + * Returns a message in the appropriate language. + * @access protected + * @return string + */ + protected function Lang($key) { + if(count($this->language) < 1) { + $this->SetLanguage('en'); // set the default language + } + + if(isset($this->language[$key])) { + return $this->language[$key]; + } else { + return 'Language string failed to load: ' . $key; + } + } + + /** + * Returns true if an error occurred. + * @access public + * @return bool + */ + public function IsError() { + return ($this->error_count > 0); + } + + /** + * Changes every end of line from CR or LF to CRLF. + * @access public + * @return string + */ + public function FixEOL($str) { + $str = str_replace("\r\n", "\n", $str); + $str = str_replace("\r", "\n", $str); + $str = str_replace("\n", $this->LE, $str); + return $str; + } + + /** + * Adds a custom header. + * @access public + * @return void + */ + public function AddCustomHeader($custom_header) { + $this->CustomHeader[] = explode(':', $custom_header, 2); + } + + /** + * Evaluates the message and returns modifications for inline images and backgrounds + * @access public + * @return $message + */ + public function MsgHTML($message, $basedir = '') { + preg_match_all("/(src|background)=\"(.*)\"/Ui", $message, $images); + if(isset($images[2])) { + foreach($images[2] as $i => $url) { + // do not change urls for absolute images (thanks to corvuscorax) + if (!preg_match('#^[A-z]+://#', $url)) { + $filename = basename($url); + $directory = dirname($url); + ($directory == '.') ? $directory='': ''; + $cid = 'cid:' . md5($filename); + $ext = pathinfo($filename, PATHINFO_EXTENSION); + $mimeType = self::_mime_types($ext); + if ( strlen($basedir) > 1 && substr($basedir, -1) != '/') { $basedir .= '/'; } + if ( strlen($directory) > 1 && substr($directory, -1) != '/') { $directory .= '/'; } + if ( $this->AddEmbeddedImage($basedir.$directory.$filename, md5($filename), $filename, 'base64', $mimeType) ) { + $message = preg_replace("/".$images[1][$i]."=\"".preg_quote($url, '/')."\"/Ui", $images[1][$i]."=\"".$cid."\"", $message); + } + } + } + } + $this->IsHTML(true); + $this->Body = $message; + $textMsg = trim(strip_tags(preg_replace('/<(head|title|style|script)[^>]*>.*?<\/\\1>/s', '', $message))); + if (!empty($textMsg) && empty($this->AltBody)) { + $this->AltBody = html_entity_decode($textMsg); + } + if (empty($this->AltBody)) { + $this->AltBody = 'To view this email message, open it in a program that understands HTML!' . "\n\n"; + } + } + + /** + * Gets the MIME type of the embedded or inline image + * @param string File extension + * @access public + * @return string MIME type of ext + * @static + */ + public static function _mime_types($ext = '') { + $mimes = array( + 'hqx' => 'application/mac-binhex40', + 'cpt' => 'application/mac-compactpro', + 'doc' => 'application/msword', + 'bin' => 'application/macbinary', + 'dms' => 'application/octet-stream', + 'lha' => 'application/octet-stream', + 'lzh' => 'application/octet-stream', + 'exe' => 'application/octet-stream', + 'class' => 'application/octet-stream', + 'psd' => 'application/octet-stream', + 'so' => 'application/octet-stream', + 'sea' => 'application/octet-stream', + 'dll' => 'application/octet-stream', + 'oda' => 'application/oda', + 'pdf' => 'application/pdf', + 'ai' => 'application/postscript', + 'eps' => 'application/postscript', + 'ps' => 'application/postscript', + 'smi' => 'application/smil', + 'smil' => 'application/smil', + 'mif' => 'application/vnd.mif', + 'xls' => 'application/vnd.ms-excel', + 'ppt' => 'application/vnd.ms-powerpoint', + 'wbxml' => 'application/vnd.wap.wbxml', + 'wmlc' => 'application/vnd.wap.wmlc', + 'dcr' => 'application/x-director', + 'dir' => 'application/x-director', + 'dxr' => 'application/x-director', + 'dvi' => 'application/x-dvi', + 'gtar' => 'application/x-gtar', + 'php' => 'application/x-httpd-php', + 'php4' => 'application/x-httpd-php', + 'php3' => 'application/x-httpd-php', + 'phtml' => 'application/x-httpd-php', + 'phps' => 'application/x-httpd-php-source', + 'js' => 'application/x-javascript', + 'swf' => 'application/x-shockwave-flash', + 'sit' => 'application/x-stuffit', + 'tar' => 'application/x-tar', + 'tgz' => 'application/x-tar', + 'xhtml' => 'application/xhtml+xml', + 'xht' => 'application/xhtml+xml', + 'zip' => 'application/zip', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mpga' => 'audio/mpeg', + 'mp2' => 'audio/mpeg', + 'mp3' => 'audio/mpeg', + 'aif' => 'audio/x-aiff', + 'aiff' => 'audio/x-aiff', + 'aifc' => 'audio/x-aiff', + 'ram' => 'audio/x-pn-realaudio', + 'rm' => 'audio/x-pn-realaudio', + 'rpm' => 'audio/x-pn-realaudio-plugin', + 'ra' => 'audio/x-realaudio', + 'rv' => 'video/vnd.rn-realvideo', + 'wav' => 'audio/x-wav', + 'bmp' => 'image/bmp', + 'gif' => 'image/gif', + 'jpeg' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'jpe' => 'image/jpeg', + 'png' => 'image/png', + 'tiff' => 'image/tiff', + 'tif' => 'image/tiff', + 'css' => 'text/css', + 'html' => 'text/html', + 'htm' => 'text/html', + 'shtml' => 'text/html', + 'txt' => 'text/plain', + 'text' => 'text/plain', + 'log' => 'text/plain', + 'rtx' => 'text/richtext', + 'rtf' => 'text/rtf', + 'xml' => 'text/xml', + 'xsl' => 'text/xml', + 'mpeg' => 'video/mpeg', + 'mpg' => 'video/mpeg', + 'mpe' => 'video/mpeg', + 'qt' => 'video/quicktime', + 'mov' => 'video/quicktime', + 'avi' => 'video/x-msvideo', + 'movie' => 'video/x-sgi-movie', + 'doc' => 'application/msword', + 'word' => 'application/msword', + 'xl' => 'application/excel', + 'eml' => 'message/rfc822' + ); + return (!isset($mimes[strtolower($ext)])) ? 'application/octet-stream' : $mimes[strtolower($ext)]; + } + + /** + * Set (or reset) Class Objects (variables) + * + * Usage Example: + * $page->set('X-Priority', '3'); + * + * @access public + * @param string $name Parameter Name + * @param mixed $value Parameter Value + * NOTE: will not work with arrays, there are no arrays to set/reset + * @todo Should this not be using __set() magic function? + */ + public function set($name, $value = '') { + try { + if (isset($this->$name) ) { + $this->$name = $value; + } else { + throw new phpmailerException($this->Lang('variable_set') . $name, self::STOP_CRITICAL); + } + } catch (Exception $e) { + $this->SetError($e->getMessage()); + if ($e->getCode() == self::STOP_CRITICAL) { + return false; + } + } + return true; + } + + /** + * Strips newlines to prevent header injection. + * @access public + * @param string $str String + * @return string + */ + public function SecureHeader($str) { + $str = str_replace("\r", '', $str); + $str = str_replace("\n", '', $str); + return trim($str); + } + + /** + * Set the private key file and password to sign the message. + * + * @access public + * @param string $key_filename Parameter File Name + * @param string $key_pass Password for private key + */ + public function Sign($cert_filename, $key_filename, $key_pass) { + $this->sign_cert_file = $cert_filename; + $this->sign_key_file = $key_filename; + $this->sign_key_pass = $key_pass; + } + + /** + * Set the private key file and password to sign the message. + * + * @access public + * @param string $key_filename Parameter File Name + * @param string $key_pass Password for private key + */ + public function DKIM_QP($txt) { + $tmp = ''; + $line = ''; + for ($i = 0; $i < strlen($txt); $i++) { + $ord = ord($txt[$i]); + if ( ((0x21 <= $ord) && ($ord <= 0x3A)) || $ord == 0x3C || ((0x3E <= $ord) && ($ord <= 0x7E)) ) { + $line .= $txt[$i]; + } else { + $line .= "=".sprintf("%02X", $ord); + } + } + return $line; + } + + /** + * Generate DKIM signature + * + * @access public + * @param string $s Header + */ + public function DKIM_Sign($s) { + $privKeyStr = file_get_contents($this->DKIM_private); + if ($this->DKIM_passphrase != '') { + $privKey = openssl_pkey_get_private($privKeyStr, $this->DKIM_passphrase); + } else { + $privKey = $privKeyStr; + } + if (openssl_sign($s, $signature, $privKey)) { + return base64_encode($signature); + } + } + + /** + * Generate DKIM Canonicalization Header + * + * @access public + * @param string $s Header + */ + public function DKIM_HeaderC($s) { + $s = preg_replace("/\r\n\s+/", " ", $s); + $lines = explode("\r\n", $s); + foreach ($lines as $key => $line) { + list($heading, $value) = explode(":", $line, 2); + $heading = strtolower($heading); + $value = preg_replace("/\s+/", " ", $value) ; // Compress useless spaces + $lines[$key] = $heading.":".trim($value) ; // Don't forget to remove WSP around the value + } + $s = implode("\r\n", $lines); + return $s; + } + + /** + * Generate DKIM Canonicalization Body + * + * @access public + * @param string $body Message Body + */ + public function DKIM_BodyC($body) { + if ($body == '') return "\r\n"; + // stabilize line endings + $body = str_replace("\r\n", "\n", $body); + $body = str_replace("\n", "\r\n", $body); + // END stabilize line endings + while (substr($body, strlen($body) - 4, 4) == "\r\n\r\n") { + $body = substr($body, 0, strlen($body) - 2); + } + return $body; + } + + /** + * Create the DKIM header, body, as new header + * + * @access public + * @param string $headers_line Header lines + * @param string $subject Subject + * @param string $body Body + */ + public function DKIM_Add($headers_line, $subject, $body) { + $DKIMsignatureType = 'rsa-sha1'; // Signature & hash algorithms + $DKIMcanonicalization = 'relaxed/simple'; // Canonicalization of header/body + $DKIMquery = 'dns/txt'; // Query method + $DKIMtime = time() ; // Signature Timestamp = seconds since 00:00:00 - Jan 1, 1970 (UTC time zone) + $subject_header = "Subject: $subject"; + $headers = explode($this->LE, $headers_line); + foreach($headers as $header) { + if (strpos($header, 'From:') === 0) { + $from_header = $header; + } elseif (strpos($header, 'To:') === 0) { + $to_header = $header; + } + } + $from = str_replace('|', '=7C', $this->DKIM_QP($from_header)); + $to = str_replace('|', '=7C', $this->DKIM_QP($to_header)); + $subject = str_replace('|', '=7C', $this->DKIM_QP($subject_header)) ; // Copied header fields (dkim-quoted-printable + $body = $this->DKIM_BodyC($body); + $DKIMlen = strlen($body) ; // Length of body + $DKIMb64 = base64_encode(pack("H*", sha1($body))) ; // Base64 of packed binary SHA-1 hash of body + $ident = ($this->DKIM_identity == '')? '' : " i=" . $this->DKIM_identity . ";"; + $dkimhdrs = "DKIM-Signature: v=1; a=" . $DKIMsignatureType . "; q=" . $DKIMquery . "; l=" . $DKIMlen . "; s=" . $this->DKIM_selector . ";\r\n". + "\tt=" . $DKIMtime . "; c=" . $DKIMcanonicalization . ";\r\n". + "\th=From:To:Subject;\r\n". + "\td=" . $this->DKIM_domain . ";" . $ident . "\r\n". + "\tz=$from\r\n". + "\t|$to\r\n". + "\t|$subject;\r\n". + "\tbh=" . $DKIMb64 . ";\r\n". + "\tb="; + $toSign = $this->DKIM_HeaderC($from_header . "\r\n" . $to_header . "\r\n" . $subject_header . "\r\n" . $dkimhdrs); + $signed = $this->DKIM_Sign($toSign); + return "X-PHPMAILER-DKIM: phpmailer.worxware.com\r\n".$dkimhdrs.$signed."\r\n"; + } + + protected function doCallback($isSent, $to, $cc, $bcc, $subject, $body) { + if (!empty($this->action_function) && function_exists($this->action_function)) { + $params = array($isSent, $to, $cc, $bcc, $subject, $body); + call_user_func_array($this->action_function, $params); + } + } +} + +class phpmailerException extends Exception { + public function errorMessage() { + $errorMsg = '' . $this->getMessage() . "
\n"; + return $errorMsg; + } +} +?> diff --git a/3rdparty/class.smtp.php b/3rdparty/class.smtp.php new file mode 100644 index 0000000000..07c275936c --- /dev/null +++ b/3rdparty/class.smtp.php @@ -0,0 +1,817 @@ +smtp_conn = 0; + $this->error = null; + $this->helo_rply = null; + + $this->do_debug = 0; + } + + ///////////////////////////////////////////////// + // CONNECTION FUNCTIONS + ///////////////////////////////////////////////// + + /** + * Connect to the server specified on the port specified. + * If the port is not specified use the default SMTP_PORT. + * If tval is specified then a connection will try and be + * established with the server for that number of seconds. + * If tval is not specified the default is 30 seconds to + * try on the connection. + * + * SMTP CODE SUCCESS: 220 + * SMTP CODE FAILURE: 421 + * @access public + * @return bool + */ + public function Connect($host, $port = 0, $tval = 30) { + // set the error val to null so there is no confusion + $this->error = null; + + // make sure we are __not__ connected + if($this->connected()) { + // already connected, generate error + $this->error = array("error" => "Already connected to a server"); + return false; + } + + if(empty($port)) { + $port = $this->SMTP_PORT; + } + + // connect to the smtp server + $this->smtp_conn = @fsockopen($host, // the host of the server + $port, // the port to use + $errno, // error number if any + $errstr, // error message if any + $tval); // give up after ? secs + // verify we connected properly + if(empty($this->smtp_conn)) { + $this->error = array("error" => "Failed to connect to server", + "errno" => $errno, + "errstr" => $errstr); + if($this->do_debug >= 1) { + echo "SMTP -> ERROR: " . $this->error["error"] . ": $errstr ($errno)" . $this->CRLF . '
'; + } + return false; + } + + // SMTP server can take longer to respond, give longer timeout for first read + // Windows does not have support for this timeout function + if(substr(PHP_OS, 0, 3) != "WIN") + socket_set_timeout($this->smtp_conn, $tval, 0); + + // get any announcement + $announce = $this->get_lines(); + + if($this->do_debug >= 2) { + echo "SMTP -> FROM SERVER:" . $announce . $this->CRLF . '
'; + } + + return true; + } + + /** + * Initiate a TLS communication with the server. + * + * SMTP CODE 220 Ready to start TLS + * SMTP CODE 501 Syntax error (no parameters allowed) + * SMTP CODE 454 TLS not available due to temporary reason + * @access public + * @return bool success + */ + public function StartTLS() { + $this->error = null; # to avoid confusion + + if(!$this->connected()) { + $this->error = array("error" => "Called StartTLS() without being connected"); + return false; + } + + fputs($this->smtp_conn,"STARTTLS" . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply,0,3); + + if($this->do_debug >= 2) { + echo "SMTP -> FROM SERVER:" . $rply . $this->CRLF . '
'; + } + + if($code != 220) { + $this->error = + array("error" => "STARTTLS not accepted from server", + "smtp_code" => $code, + "smtp_msg" => substr($rply,4)); + if($this->do_debug >= 1) { + echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
'; + } + return false; + } + + // Begin encrypted connection + if(!stream_socket_enable_crypto($this->smtp_conn, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) { + return false; + } + + return true; + } + + /** + * Performs SMTP authentication. Must be run after running the + * Hello() method. Returns true if successfully authenticated. + * @access public + * @return bool + */ + public function Authenticate($username, $password) { + // Start authentication + fputs($this->smtp_conn,"AUTH LOGIN" . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply,0,3); + + if($code != 334) { + $this->error = + array("error" => "AUTH not accepted from server", + "smtp_code" => $code, + "smtp_msg" => substr($rply,4)); + if($this->do_debug >= 1) { + echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
'; + } + return false; + } + + // Send encoded username + fputs($this->smtp_conn, base64_encode($username) . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply,0,3); + + if($code != 334) { + $this->error = + array("error" => "Username not accepted from server", + "smtp_code" => $code, + "smtp_msg" => substr($rply,4)); + if($this->do_debug >= 1) { + echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
'; + } + return false; + } + + // Send encoded password + fputs($this->smtp_conn, base64_encode($password) . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply,0,3); + + if($code != 235) { + $this->error = + array("error" => "Password not accepted from server", + "smtp_code" => $code, + "smtp_msg" => substr($rply,4)); + if($this->do_debug >= 1) { + echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
'; + } + return false; + } + + return true; + } + + /** + * Returns true if connected to a server otherwise false + * @access public + * @return bool + */ + public function Connected() { + if(!empty($this->smtp_conn)) { + $sock_status = socket_get_status($this->smtp_conn); + if($sock_status["eof"]) { + // the socket is valid but we are not connected + if($this->do_debug >= 1) { + echo "SMTP -> NOTICE:" . $this->CRLF . "EOF caught while checking if connected"; + } + $this->Close(); + return false; + } + return true; // everything looks good + } + return false; + } + + /** + * Closes the socket and cleans up the state of the class. + * It is not considered good to use this function without + * first trying to use QUIT. + * @access public + * @return void + */ + public function Close() { + $this->error = null; // so there is no confusion + $this->helo_rply = null; + if(!empty($this->smtp_conn)) { + // close the connection and cleanup + fclose($this->smtp_conn); + $this->smtp_conn = 0; + } + } + + ///////////////////////////////////////////////// + // SMTP COMMANDS + ///////////////////////////////////////////////// + + /** + * Issues a data command and sends the msg_data to the server + * finializing the mail transaction. $msg_data is the message + * that is to be send with the headers. Each header needs to be + * on a single line followed by a with the message headers + * and the message body being seperated by and additional . + * + * Implements rfc 821: DATA + * + * SMTP CODE INTERMEDIATE: 354 + * [data] + * . + * SMTP CODE SUCCESS: 250 + * SMTP CODE FAILURE: 552,554,451,452 + * SMTP CODE FAILURE: 451,554 + * SMTP CODE ERROR : 500,501,503,421 + * @access public + * @return bool + */ + public function Data($msg_data) { + $this->error = null; // so no confusion is caused + + if(!$this->connected()) { + $this->error = array( + "error" => "Called Data() without being connected"); + return false; + } + + fputs($this->smtp_conn,"DATA" . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply,0,3); + + if($this->do_debug >= 2) { + echo "SMTP -> FROM SERVER:" . $rply . $this->CRLF . '
'; + } + + if($code != 354) { + $this->error = + array("error" => "DATA command not accepted from server", + "smtp_code" => $code, + "smtp_msg" => substr($rply,4)); + if($this->do_debug >= 1) { + echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
'; + } + return false; + } + + /* the server is ready to accept data! + * according to rfc 821 we should not send more than 1000 + * including the CRLF + * characters on a single line so we will break the data up + * into lines by \r and/or \n then if needed we will break + * each of those into smaller lines to fit within the limit. + * in addition we will be looking for lines that start with + * a period '.' and append and additional period '.' to that + * line. NOTE: this does not count towards limit. + */ + + // normalize the line breaks so we know the explode works + $msg_data = str_replace("\r\n","\n",$msg_data); + $msg_data = str_replace("\r","\n",$msg_data); + $lines = explode("\n",$msg_data); + + /* we need to find a good way to determine is headers are + * in the msg_data or if it is a straight msg body + * currently I am assuming rfc 822 definitions of msg headers + * and if the first field of the first line (':' sperated) + * does not contain a space then it _should_ be a header + * and we can process all lines before a blank "" line as + * headers. + */ + + $field = substr($lines[0],0,strpos($lines[0],":")); + $in_headers = false; + if(!empty($field) && !strstr($field," ")) { + $in_headers = true; + } + + $max_line_length = 998; // used below; set here for ease in change + + while(list(,$line) = @each($lines)) { + $lines_out = null; + if($line == "" && $in_headers) { + $in_headers = false; + } + // ok we need to break this line up into several smaller lines + while(strlen($line) > $max_line_length) { + $pos = strrpos(substr($line,0,$max_line_length)," "); + + // Patch to fix DOS attack + if(!$pos) { + $pos = $max_line_length - 1; + $lines_out[] = substr($line,0,$pos); + $line = substr($line,$pos); + } else { + $lines_out[] = substr($line,0,$pos); + $line = substr($line,$pos + 1); + } + + /* if processing headers add a LWSP-char to the front of new line + * rfc 822 on long msg headers + */ + if($in_headers) { + $line = "\t" . $line; + } + } + $lines_out[] = $line; + + // send the lines to the server + while(list(,$line_out) = @each($lines_out)) { + if(strlen($line_out) > 0) + { + if(substr($line_out, 0, 1) == ".") { + $line_out = "." . $line_out; + } + } + fputs($this->smtp_conn,$line_out . $this->CRLF); + } + } + + // message data has been sent + fputs($this->smtp_conn, $this->CRLF . "." . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply,0,3); + + if($this->do_debug >= 2) { + echo "SMTP -> FROM SERVER:" . $rply . $this->CRLF . '
'; + } + + if($code != 250) { + $this->error = + array("error" => "DATA not accepted from server", + "smtp_code" => $code, + "smtp_msg" => substr($rply,4)); + if($this->do_debug >= 1) { + echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
'; + } + return false; + } + return true; + } + + /** + * Sends the HELO command to the smtp server. + * This makes sure that we and the server are in + * the same known state. + * + * Implements from rfc 821: HELO + * + * SMTP CODE SUCCESS: 250 + * SMTP CODE ERROR : 500, 501, 504, 421 + * @access public + * @return bool + */ + public function Hello($host = '') { + $this->error = null; // so no confusion is caused + + if(!$this->connected()) { + $this->error = array( + "error" => "Called Hello() without being connected"); + return false; + } + + // if hostname for HELO was not specified send default + if(empty($host)) { + // determine appropriate default to send to server + $host = "localhost"; + } + + // Send extended hello first (RFC 2821) + if(!$this->SendHello("EHLO", $host)) { + if(!$this->SendHello("HELO", $host)) { + return false; + } + } + + return true; + } + + /** + * Sends a HELO/EHLO command. + * @access private + * @return bool + */ + private function SendHello($hello, $host) { + fputs($this->smtp_conn, $hello . " " . $host . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply,0,3); + + if($this->do_debug >= 2) { + echo "SMTP -> FROM SERVER: " . $rply . $this->CRLF . '
'; + } + + if($code != 250) { + $this->error = + array("error" => $hello . " not accepted from server", + "smtp_code" => $code, + "smtp_msg" => substr($rply,4)); + if($this->do_debug >= 1) { + echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
'; + } + return false; + } + + $this->helo_rply = $rply; + + return true; + } + + /** + * Starts a mail transaction from the email address specified in + * $from. Returns true if successful or false otherwise. If True + * the mail transaction is started and then one or more Recipient + * commands may be called followed by a Data command. + * + * Implements rfc 821: MAIL FROM: + * + * SMTP CODE SUCCESS: 250 + * SMTP CODE SUCCESS: 552,451,452 + * SMTP CODE SUCCESS: 500,501,421 + * @access public + * @return bool + */ + public function Mail($from) { + $this->error = null; // so no confusion is caused + + if(!$this->connected()) { + $this->error = array( + "error" => "Called Mail() without being connected"); + return false; + } + + $useVerp = ($this->do_verp ? "XVERP" : ""); + fputs($this->smtp_conn,"MAIL FROM:<" . $from . ">" . $useVerp . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply,0,3); + + if($this->do_debug >= 2) { + echo "SMTP -> FROM SERVER:" . $rply . $this->CRLF . '
'; + } + + if($code != 250) { + $this->error = + array("error" => "MAIL not accepted from server", + "smtp_code" => $code, + "smtp_msg" => substr($rply,4)); + if($this->do_debug >= 1) { + echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
'; + } + return false; + } + return true; + } + + /** + * Sends the quit command to the server and then closes the socket + * if there is no error or the $close_on_error argument is true. + * + * Implements from rfc 821: QUIT + * + * SMTP CODE SUCCESS: 221 + * SMTP CODE ERROR : 500 + * @access public + * @return bool + */ + public function Quit($close_on_error = true) { + $this->error = null; // so there is no confusion + + if(!$this->connected()) { + $this->error = array( + "error" => "Called Quit() without being connected"); + return false; + } + + // send the quit command to the server + fputs($this->smtp_conn,"quit" . $this->CRLF); + + // get any good-bye messages + $byemsg = $this->get_lines(); + + if($this->do_debug >= 2) { + echo "SMTP -> FROM SERVER:" . $byemsg . $this->CRLF . '
'; + } + + $rval = true; + $e = null; + + $code = substr($byemsg,0,3); + if($code != 221) { + // use e as a tmp var cause Close will overwrite $this->error + $e = array("error" => "SMTP server rejected quit command", + "smtp_code" => $code, + "smtp_rply" => substr($byemsg,4)); + $rval = false; + if($this->do_debug >= 1) { + echo "SMTP -> ERROR: " . $e["error"] . ": " . $byemsg . $this->CRLF . '
'; + } + } + + if(empty($e) || $close_on_error) { + $this->Close(); + } + + return $rval; + } + + /** + * Sends the command RCPT to the SMTP server with the TO: argument of $to. + * Returns true if the recipient was accepted false if it was rejected. + * + * Implements from rfc 821: RCPT TO: + * + * SMTP CODE SUCCESS: 250,251 + * SMTP CODE FAILURE: 550,551,552,553,450,451,452 + * SMTP CODE ERROR : 500,501,503,421 + * @access public + * @return bool + */ + public function Recipient($to) { + $this->error = null; // so no confusion is caused + + if(!$this->connected()) { + $this->error = array( + "error" => "Called Recipient() without being connected"); + return false; + } + + fputs($this->smtp_conn,"RCPT TO:<" . $to . ">" . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply,0,3); + + if($this->do_debug >= 2) { + echo "SMTP -> FROM SERVER:" . $rply . $this->CRLF . '
'; + } + + if($code != 250 && $code != 251) { + $this->error = + array("error" => "RCPT not accepted from server", + "smtp_code" => $code, + "smtp_msg" => substr($rply,4)); + if($this->do_debug >= 1) { + echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
'; + } + return false; + } + return true; + } + + /** + * Sends the RSET command to abort and transaction that is + * currently in progress. Returns true if successful false + * otherwise. + * + * Implements rfc 821: RSET + * + * SMTP CODE SUCCESS: 250 + * SMTP CODE ERROR : 500,501,504,421 + * @access public + * @return bool + */ + public function Reset() { + $this->error = null; // so no confusion is caused + + if(!$this->connected()) { + $this->error = array( + "error" => "Called Reset() without being connected"); + return false; + } + + fputs($this->smtp_conn,"RSET" . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply,0,3); + + if($this->do_debug >= 2) { + echo "SMTP -> FROM SERVER:" . $rply . $this->CRLF . '
'; + } + + if($code != 250) { + $this->error = + array("error" => "RSET failed", + "smtp_code" => $code, + "smtp_msg" => substr($rply,4)); + if($this->do_debug >= 1) { + echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
'; + } + return false; + } + + return true; + } + + /** + * Starts a mail transaction from the email address specified in + * $from. Returns true if successful or false otherwise. If True + * the mail transaction is started and then one or more Recipient + * commands may be called followed by a Data command. This command + * will send the message to the users terminal if they are logged + * in and send them an email. + * + * Implements rfc 821: SAML FROM: + * + * SMTP CODE SUCCESS: 250 + * SMTP CODE SUCCESS: 552,451,452 + * SMTP CODE SUCCESS: 500,501,502,421 + * @access public + * @return bool + */ + public function SendAndMail($from) { + $this->error = null; // so no confusion is caused + + if(!$this->connected()) { + $this->error = array( + "error" => "Called SendAndMail() without being connected"); + return false; + } + + fputs($this->smtp_conn,"SAML FROM:" . $from . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply,0,3); + + if($this->do_debug >= 2) { + echo "SMTP -> FROM SERVER:" . $rply . $this->CRLF . '
'; + } + + if($code != 250) { + $this->error = + array("error" => "SAML not accepted from server", + "smtp_code" => $code, + "smtp_msg" => substr($rply,4)); + if($this->do_debug >= 1) { + echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
'; + } + return false; + } + return true; + } + + /** + * This is an optional command for SMTP that this class does not + * support. This method is here to make the RFC821 Definition + * complete for this class and __may__ be implimented in the future + * + * Implements from rfc 821: TURN + * + * SMTP CODE SUCCESS: 250 + * SMTP CODE FAILURE: 502 + * SMTP CODE ERROR : 500, 503 + * @access public + * @return bool + */ + public function Turn() { + $this->error = array("error" => "This method, TURN, of the SMTP ". + "is not implemented"); + if($this->do_debug >= 1) { + echo "SMTP -> NOTICE: " . $this->error["error"] . $this->CRLF . '
'; + } + return false; + } + + /** + * Get the current error + * @access public + * @return array + */ + public function getError() { + return $this->error; + } + + ///////////////////////////////////////////////// + // INTERNAL FUNCTIONS + ///////////////////////////////////////////////// + + /** + * Read in as many lines as possible + * either before eof or socket timeout occurs on the operation. + * With SMTP we can tell if we have more lines to read if the + * 4th character is '-' symbol. If it is a space then we don't + * need to read anything else. + * @access private + * @return string + */ + private function get_lines() { + $data = ""; + while($str = @fgets($this->smtp_conn,515)) { + if($this->do_debug >= 4) { + echo "SMTP -> get_lines(): \$data was \"$data\"" . $this->CRLF . '
'; + echo "SMTP -> get_lines(): \$str is \"$str\"" . $this->CRLF . '
'; + } + $data .= $str; + if($this->do_debug >= 4) { + echo "SMTP -> get_lines(): \$data is \"$data\"" . $this->CRLF . '
'; + } + // if 4th character is a space, we are done reading, break the loop + if(substr($str,3,1) == " ") { break; } + } + return $data; + } + +} + +?> diff --git a/3rdparty/php-cloudfiles/.gitignore b/3rdparty/php-cloudfiles/.gitignore new file mode 100644 index 0000000000..875b72b27e --- /dev/null +++ b/3rdparty/php-cloudfiles/.gitignore @@ -0,0 +1,3 @@ +*.swp +*~ +tests/output.log diff --git a/3rdparty/php-cloudfiles/AUTHORS b/3rdparty/php-cloudfiles/AUTHORS new file mode 100644 index 0000000000..a92cfa7c1a --- /dev/null +++ b/3rdparty/php-cloudfiles/AUTHORS @@ -0,0 +1,11 @@ +Current maintainer: + Conrad Weidenkeller + +Previous maintainer: + Eric "EJ" Johnson + Chmouel Boudjnah + +Contributors: + Paul Kehrer + Ben Arwin + Jordan Callicoat diff --git a/3rdparty/php-cloudfiles/COPYING b/3rdparty/php-cloudfiles/COPYING new file mode 100644 index 0000000000..0e10239d00 --- /dev/null +++ b/3rdparty/php-cloudfiles/COPYING @@ -0,0 +1,27 @@ +Unless otherwise noted, all files are released under the MIT license, +exceptions contain licensing information in them. + + Copyright (C) 2008 Rackspace US, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +Except as contained in this notice, the name of Rackspace US, Inc. shall not +be used in advertising or otherwise to promote the sale, use or other dealings +in this Software without prior written authorization from Rackspace US, Inc. + diff --git a/3rdparty/php-cloudfiles/Changelog b/3rdparty/php-cloudfiles/Changelog new file mode 100644 index 0000000000..df9303c3e5 --- /dev/null +++ b/3rdparty/php-cloudfiles/Changelog @@ -0,0 +1,93 @@ +1.7.10 Conrad Weidenkeller + * Added Streaming URI Functionality + +1.7.9 Conrad Weidenkeller + * Added Manifest file support for Large Objects + +1.7.8 Conrad Weidenkeller + * Added CDN SSL URI Stuff + +1.7.7 Conrad Weidenkeller + * Added CDN Purge Functionality + +1.7.6 - Chmouel Boudjnah + * Add Cloud UK Support (conrad.weidenkeller). + +1.7.5 - Conrad Weidenkeller + * Added the ability to list only currently enabled CDN containers + * Added curl timeout to CF_Http + * Fixed some logic errors in some if statements. + +1.7.4 - Conrad Weidenkeller + * Added Manual SSL support for MacOSX users + +1.7.3 - Conrad Weidenkeller + * Fixed a Small Bug where some users were seeing response bodies for PUTs + +1.7.1 - Conrad Weidenkeller + * Added Support for Auth Token Caching. + +1.7.0 - Chmouel Boudjnah + * Adjust api auth to rackspacecloud not mosso (mshuler). + +1.6.2 - Chmouel Boudjnah + * Add a close method to close all the current connection. + * Fix when container_name is named 0. + +1.6.1 - Chmouel Boudjnah + * Fix setting etag on objects. + * Fix throwing proper exception when an invalid etag has been set. + * Fix throwing proper exception when no content type has been set. + +1.6.0 - Chmouel Boudjnah + * Add CDN ACL restriction by referrer feature. + * Add CDN ACL restriction by User Agent feature. + * Add documentation for log_retention method. + * Return True if log_retention as succeeded. + * Invalid the PHP stats cache before getting filesize. + +1.5.1 - Chmouel Boudjnah - 20091020 + * If the environement variable RACKSPACE_SERVICENET is defined then force to + connect via rakcspace servicenet. + +1.5.0 - Chmouel Boudjnah - 20091015 + * Add the option servicenet to connection to use Rackspace service net + instead of public network. + +1.4.0 - Chmouel Boudjnah - 20090808 + + * Add the ability to store the container log. + +1.3.2 - Chmouel Boudjnah - 20090606 + + * Change the Unit Tests to phpunit. + * Automatically set the updated CA bundle when on the windows OS. + * More simplification of the mimetype detection and support for PHP 5.3. + * Fix documentation information about the ttl for cached object. + * Use the hash library to compute MD5 for streams instead of storing the + stream in memory. + * Fix CF_Connection::get_containers to display the container name properly. + +1.3.1 - - 20090325 + + * Simplify use of FileInfo, remove packaged MIME/Magic file + * Throw Exception if no Content-Type is set + * Fix bug with tracking bytes transferred + * Support/tested on Windows XP (PHP v5.2.9) + +1.3.0 - - 20090311 + + * Support for list operations in JSON/XML + * Added support for FileInfo automatic Content-Type/MIME detection + * Workaround for cURL's old CA bundle with CF_Connection->ssl_use_cabundle() + * Supports limit/marker on Account and Container lists + * Support "pathname" traversal on Container lists + * Helper function on Container to create directory marker Objects + * Support for chunked transfer on PUT requests + +1.2.3 - - 20081210 + + * Improved in-line comments and generated HTML docs + * Callbacks for read/write progress on CF_Connection class + * Fixed minor bugs + * Started this Changelog diff --git a/3rdparty/php-cloudfiles/README b/3rdparty/php-cloudfiles/README new file mode 100644 index 0000000000..4bcbeade1a --- /dev/null +++ b/3rdparty/php-cloudfiles/README @@ -0,0 +1,73 @@ +;; PHP Cloud Files API +;; ======================================================================== +;; This package contains the PHP API for the Cloud Files storage system. +;; +;; Please see http://www.rackspacecloud.com/ for more information regarding the +;; Cloud Files storage system. +;; +;; Install +;; ------------------------------------------------------------------------ +;; Extract this archive and make sure the source code files are in your +;; PHP "include path". To use the API in your source code, just make +;; sure to include/require the "cloudfiles.php" script. +;; +;; Requirements +;; ------------------------------------------------------------------------ +;; [mandatory] PHP version 5.x (developed against 5.2.0) +;; [mandatory] PHP's cURL module +;; [mandatory] PHP enabled with mbstring (multi-byte string) support +;; [suggested] PEAR FileInfo module (for Content-Type detection) +;; +;; Examples +;; ------------------------------------------------------------------------ +;; For sample code, please see the tests and API docs. +;; +;; Docs +;; ------------------------------------------------------------------------ +;; The included documentation was generated directly from the source +;; code files using the PHPDocumentor tool. +;; +;; This README file is actually the PHPDocumentor INI configuration file. +;; The following packages were installed via PEAR to generate the HTML +;; API documentation. +;; +;; * PEAR 1.4.11 (stable) +;; * PhpDocumentor 1.4.2 (stable) +;; * XML_Beautifier 1.2.0 (stable) +;; * XML_Parser 1.3.1 (stable) +;; * XML_Util 1.2.0 (stable) +;; +;; To re-generate the API docs, make sure the above software is +;; available and run: +;; rm -rf docs && phpdoc -c phpdoc.ini +;; +;; Tests +;; ------------------------------------------------------------------------ +;; The tests are based on phpunit and are run with PHPUnit 3.3.17 +;; please follow the instructions on : +;; +;; http://www.phpunit.de/manual/current/en/installation.html +;; +;; to install PHPUnit. When installed just run the command phpunit on +;; the top of the directory and it will launch the tests. +;; +;; The tests/Comprehensive.php is not enabled by default since +;; generating big files. If you want to run it you need to go in the +;; tests directory and run with phpunit Comprehensive.php +;; +;; ======================================================================== +;; The lines below here are the configuration settings for re-generating +;; the PHP API documentation. +;; +[Parse Data] +title = php-cloudfiles +hidden = false +parseprivate = off +javadocdesc = off +defaultpackagename = php-cloudfiles +defaultcategoryname = php-cloudfiles +target = docs +directory = . +ignore = share/,examples/,tests/,.git/,.gitignore,*.ini,*.swp +output=HTML:Smarty:PHP +readmeinstallchangelog = README,COPYING,AUTHORS,Changelog diff --git a/3rdparty/php-cloudfiles/cloudfiles.php b/3rdparty/php-cloudfiles/cloudfiles.php new file mode 100644 index 0000000000..5f7e2100a9 --- /dev/null +++ b/3rdparty/php-cloudfiles/cloudfiles.php @@ -0,0 +1,2599 @@ + + * # Authenticate to Cloud Files. The default is to automatically try + * # to re-authenticate if an authentication token expires. + * # + * # NOTE: Some versions of cURL include an outdated certificate authority (CA) + * # file. This API ships with a newer version obtained directly from + * # cURL's web site (http://curl.haxx.se). To use the newer CA bundle, + * # call the CF_Authentication instance's 'ssl_use_cabundle()' method. + * # + * $auth = new CF_Authentication($username, $api_key); + * # $auth->ssl_use_cabundle(); # bypass cURL's old CA bundle + * $auth->authenticate(); + * + * # Establish a connection to the storage system + * # + * # NOTE: Some versions of cURL include an outdated certificate authority (CA) + * # file. This API ships with a newer version obtained directly from + * # cURL's web site (http://curl.haxx.se). To use the newer CA bundle, + * # call the CF_Connection instance's 'ssl_use_cabundle()' method. + * # + * $conn = new CF_Connection($auth); + * # $conn->ssl_use_cabundle(); # bypass cURL's old CA bundle + * + * # Create a remote Container and storage Object + * # + * $images = $conn->create_container("photos"); + * $bday = $images->create_object("first_birthday.jpg"); + * + * # Upload content from a local file by streaming it. Note that we use + * # a "float" for the file size to overcome PHP's 32-bit integer limit for + * # very large files. + * # + * $fname = "/home/user/photos/birthdays/birthday1.jpg"; # filename to upload + * $size = (float) sprintf("%u", filesize($fname)); + * $fp = open($fname, "r"); + * $bday->write($fp, $size); + * + * # Or... use a convenience function instead + * # + * $bday->load_from_filename("/home/user/photos/birthdays/birthday1.jpg"); + * + * # Now, publish the "photos" container to serve the images by CDN. + * # Use the "$uri" value to put in your web pages or send the link in an + * # email message, etc. + * # + * $uri = $images->make_public(); + * + * # Or... print out the Object's public URI + * # + * print $bday->public_uri(); + * + * + * See the included tests directory for additional sample code. + * + * Requres PHP 5.x (for Exceptions and OO syntax) and PHP's cURL module. + * + * It uses the supporting "cloudfiles_http.php" module for HTTP(s) support and + * allows for connection re-use and streaming of content into/out of Cloud Files + * via PHP's cURL module. + * + * See COPYING for license information. + * + * @author Eric "EJ" Johnson + * @copyright Copyright (c) 2008, Rackspace US, Inc. + * @package php-cloudfiles + */ + +/** + */ +require_once("cloudfiles_exceptions.php"); +require("cloudfiles_http.php"); +define("DEFAULT_CF_API_VERSION", 1); +define("MAX_CONTAINER_NAME_LEN", 256); +define("MAX_OBJECT_NAME_LEN", 1024); +define("MAX_OBJECT_SIZE", 5*1024*1024*1024+1); +define("US_AUTHURL", "https://auth.api.rackspacecloud.com"); +define("UK_AUTHURL", "https://lon.auth.api.rackspacecloud.com"); +/** + * Class for handling Cloud Files Authentication, call it's {@link authenticate()} + * method to obtain authorized service urls and an authentication token. + * + * Example: + * + * # Create the authentication instance + * # + * $auth = new CF_Authentication("username", "api_key"); + * + * # NOTE: For UK Customers please specify your AuthURL Manually + * # There is a Predfined constant to use EX: + * # + * # $auth = new CF_Authentication("username, "api_key", NULL, UK_AUTHURL); + * # Using the UK_AUTHURL keyword will force the api to use the UK AuthUrl. + * # rather then the US one. The NULL Is passed for legacy purposes and must + * # be passed to function correctly. + * + * # NOTE: Some versions of cURL include an outdated certificate authority (CA) + * # file. This API ships with a newer version obtained directly from + * # cURL's web site (http://curl.haxx.se). To use the newer CA bundle, + * # call the CF_Authentication instance's 'ssl_use_cabundle()' method. + * # + * # $auth->ssl_use_cabundle(); # bypass cURL's old CA bundle + * + * # Perform authentication request + * # + * $auth->authenticate(); + * + * + * @package php-cloudfiles + */ +class CF_Authentication +{ + public $dbug; + public $username; + public $api_key; + public $auth_host; + public $account; + + /** + * Instance variables that are set after successful authentication + */ + public $storage_url; + public $cdnm_url; + public $auth_token; + + /** + * Class constructor (PHP 5 syntax) + * + * @param string $username Mosso username + * @param string $api_key Mosso API Access Key + * @param string $account Account name + * @param string $auth_host Authentication service URI + */ + function __construct($username=NULL, $api_key=NULL, $account=NULL, $auth_host=US_AUTHURL) + { + + $this->dbug = False; + $this->username = $username; + $this->api_key = $api_key; + $this->account_name = $account; + $this->auth_host = $auth_host; + + $this->storage_url = NULL; + $this->cdnm_url = NULL; + $this->auth_token = NULL; + + $this->cfs_http = new CF_Http(DEFAULT_CF_API_VERSION); + } + + /** + * Use the Certificate Authority bundle included with this API + * + * Most versions of PHP with cURL support include an outdated Certificate + * Authority (CA) bundle (the file that lists all valid certificate + * signing authorities). The SSL certificates used by the Cloud Files + * storage system are perfectly valid but have been created/signed by + * a CA not listed in these outdated cURL distributions. + * + * As a work-around, we've included an updated CA bundle obtained + * directly from cURL's web site (http://curl.haxx.se). You can direct + * the API to use this CA bundle by calling this method prior to making + * any remote calls. The best place to use this method is right after + * the CF_Authentication instance has been instantiated. + * + * You can specify your own CA bundle by passing in the full pathname + * to the bundle. You can use the included CA bundle by leaving the + * argument blank. + * + * @param string $path Specify path to CA bundle (default to included) + */ + function ssl_use_cabundle($path=NULL) + { + $this->cfs_http->ssl_use_cabundle($path); + } + + /** + * Attempt to validate Username/API Access Key + * + * Attempts to validate credentials with the authentication service. It + * either returns True or throws an Exception. Accepts a single + * (optional) argument for the storage system API version. + * + * Example: + * + * # Create the authentication instance + * # + * $auth = new CF_Authentication("username", "api_key"); + * + * # Perform authentication request + * # + * $auth->authenticate(); + * + * + * @param string $version API version for Auth service (optional) + * @return boolean True if successfully authenticated + * @throws AuthenticationException invalid credentials + * @throws InvalidResponseException invalid response + */ + function authenticate($version=DEFAULT_CF_API_VERSION) + { + list($status,$reason,$surl,$curl,$atoken) = + $this->cfs_http->authenticate($this->username, $this->api_key, + $this->account_name, $this->auth_host); + + if ($status == 401) { + throw new AuthenticationException("Invalid username or access key."); + } + if ($status < 200 || $status > 299) { + throw new InvalidResponseException( + "Unexpected response (".$status."): ".$reason); + } + + if (!($surl || $curl) || !$atoken) { + throw new InvalidResponseException( + "Expected headers missing from auth service."); + } + $this->storage_url = $surl; + $this->cdnm_url = $curl; + $this->auth_token = $atoken; + return True; + } + /** + * Use Cached Token and Storage URL's rather then grabbing from the Auth System + * + * Example: + * + * #Create an Auth instance + * $auth = new CF_Authentication(); + * #Pass Cached URL's and Token as Args + * $auth->load_cached_credentials("auth_token", "storage_url", "cdn_management_url"); + * + * + * @param string $auth_token A Cloud Files Auth Token (Required) + * @param string $storage_url The Cloud Files Storage URL (Required) + * @param string $cdnm_url CDN Management URL (Required) + * @return boolean True if successful + * @throws SyntaxException If any of the Required Arguments are missing + */ + function load_cached_credentials($auth_token, $storage_url, $cdnm_url) + { + if(!$storage_url || !$cdnm_url) + { + throw new SyntaxException("Missing Required Interface URL's!"); + return False; + } + if(!$auth_token) + { + throw new SyntaxException("Missing Auth Token!"); + return False; + } + + $this->storage_url = $storage_url; + $this->cdnm_url = $cdnm_url; + $this->auth_token = $auth_token; + return True; + } + /** + * Grab Cloud Files info to be Cached for later use with the load_cached_credentials method. + * + * Example: + * + * #Create an Auth instance + * $auth = new CF_Authentication("UserName","API_Key"); + * $auth->authenticate(); + * $array = $auth->export_credentials(); + * + * + * @return array of url's and an auth token. + */ + function export_credentials() + { + $arr = array(); + $arr['storage_url'] = $this->storage_url; + $arr['cdnm_url'] = $this->cdnm_url; + $arr['auth_token'] = $this->auth_token; + + return $arr; + } + + + /** + * Make sure the CF_Authentication instance has authenticated. + * + * Ensures that the instance variables necessary to communicate with + * Cloud Files have been set from a previous authenticate() call. + * + * @return boolean True if successfully authenticated + */ + function authenticated() + { + if (!($this->storage_url || $this->cdnm_url) || !$this->auth_token) { + return False; + } + return True; + } + + /** + * Toggle debugging - set cURL verbose flag + */ + function setDebug($bool) + { + $this->dbug = $bool; + $this->cfs_http->setDebug($bool); + } +} + +/** + * Class for establishing connections to the Cloud Files storage system. + * Connection instances are used to communicate with the storage system at + * the account level; listing and deleting Containers and returning Container + * instances. + * + * Example: + * + * # Create the authentication instance + * # + * $auth = new CF_Authentication("username", "api_key"); + * + * # Perform authentication request + * # + * $auth->authenticate(); + * + * # Create a connection to the storage/cdn system(s) and pass in the + * # validated CF_Authentication instance. + * # + * $conn = new CF_Connection($auth); + * + * # NOTE: Some versions of cURL include an outdated certificate authority (CA) + * # file. This API ships with a newer version obtained directly from + * # cURL's web site (http://curl.haxx.se). To use the newer CA bundle, + * # call the CF_Authentication instance's 'ssl_use_cabundle()' method. + * # + * # $conn->ssl_use_cabundle(); # bypass cURL's old CA bundle + * + * + * @package php-cloudfiles + */ +class CF_Connection +{ + public $dbug; + public $cfs_http; + public $cfs_auth; + + /** + * Pass in a previously authenticated CF_Authentication instance. + * + * Example: + * + * # Create the authentication instance + * # + * $auth = new CF_Authentication("username", "api_key"); + * + * # Perform authentication request + * # + * $auth->authenticate(); + * + * # Create a connection to the storage/cdn system(s) and pass in the + * # validated CF_Authentication instance. + * # + * $conn = new CF_Connection($auth); + * + * # If you are connecting via Rackspace servers and have access + * # to the servicenet network you can set the $servicenet to True + * # like this. + * + * $conn = new CF_Connection($auth, $servicenet=True); + * + * + * + * If the environement variable RACKSPACE_SERVICENET is defined it will + * force to connect via the servicenet. + * + * @param obj $cfs_auth previously authenticated CF_Authentication instance + * @param boolean $servicenet enable/disable access via Rackspace servicenet. + * @throws AuthenticationException not authenticated + */ + function __construct($cfs_auth, $servicenet=False) + { + if (isset($_ENV['RACKSPACE_SERVICENET'])) + $servicenet=True; + $this->cfs_http = new CF_Http(DEFAULT_CF_API_VERSION); + $this->cfs_auth = $cfs_auth; + if (!$this->cfs_auth->authenticated()) { + $e = "Need to pass in a previously authenticated "; + $e .= "CF_Authentication instance."; + throw new AuthenticationException($e); + } + $this->cfs_http->setCFAuth($this->cfs_auth, $servicenet=$servicenet); + $this->dbug = False; + } + + /** + * Toggle debugging of instance and back-end HTTP module + * + * @param boolean $bool enable/disable cURL debugging + */ + function setDebug($bool) + { + $this->dbug = (boolean) $bool; + $this->cfs_http->setDebug($this->dbug); + } + + /** + * Close a connection + * + * Example: + * + * + * $conn->close(); + * + * + * + * Will close all current cUrl active connections. + * + */ + public function close() + { + $this->cfs_http->close(); + } + + /** + * Cloud Files account information + * + * Return an array of two floats (since PHP only supports 32-bit integers); + * number of containers on the account and total bytes used for the account. + * + * Example: + * + * # ... authentication code excluded (see previous examples) ... + * # + * $conn = new CF_Authentication($auth); + * + * list($quantity, $bytes) = $conn->get_info(); + * print "Number of containers: " . $quantity . "\n"; + * print "Bytes stored in container: " . $bytes . "\n"; + * + * + * @return array (number of containers, total bytes stored) + * @throws InvalidResponseException unexpected response + */ + function get_info() + { + list($status, $reason, $container_count, $total_bytes) = + $this->cfs_http->head_account(); + #if ($status == 401 && $this->_re_auth()) { + # return $this->get_info(); + #} + if ($status < 200 || $status > 299) { + throw new InvalidResponseException( + "Invalid response (".$status."): ".$this->cfs_http->get_error()); + } + return array($container_count, $total_bytes); + } + + /** + * Create a Container + * + * Given a Container name, return a Container instance, creating a new + * remote Container if it does not exit. + * + * Example: + * + * # ... authentication code excluded (see previous examples) ... + * # + * $conn = new CF_Authentication($auth); + * + * $images = $conn->create_container("my photos"); + * + * + * @param string $container_name container name + * @return CF_Container + * @throws SyntaxException invalid name + * @throws InvalidResponseException unexpected response + */ + function create_container($container_name=NULL) + { + if ($container_name != "0" and !isset($container_name)) + throw new SyntaxException("Container name not set."); + + if (!isset($container_name) or $container_name == "") + throw new SyntaxException("Container name not set."); + + if (strpos($container_name, "/") !== False) { + $r = "Container name '".$container_name; + $r .= "' cannot contain a '/' character."; + throw new SyntaxException($r); + } + if (strlen($container_name) > MAX_CONTAINER_NAME_LEN) { + throw new SyntaxException(sprintf( + "Container name exeeds %d bytes.", + MAX_CONTAINER_NAME_LEN)); + } + + $return_code = $this->cfs_http->create_container($container_name); + if (!$return_code) { + throw new InvalidResponseException("Invalid response (" + . $return_code. "): " . $this->cfs_http->get_error()); + } + #if ($status == 401 && $this->_re_auth()) { + # return $this->create_container($container_name); + #} + if ($return_code != 201 && $return_code != 202) { + throw new InvalidResponseException( + "Invalid response (".$return_code."): " + . $this->cfs_http->get_error()); + } + return new CF_Container($this->cfs_auth, $this->cfs_http, $container_name); + } + + /** + * Delete a Container + * + * Given either a Container instance or name, remove the remote Container. + * The Container must be empty prior to removing it. + * + * Example: + * + * # ... authentication code excluded (see previous examples) ... + * # + * $conn = new CF_Authentication($auth); + * + * $conn->delete_container("my photos"); + * + * + * @param string|obj $container container name or instance + * @return boolean True if successfully deleted + * @throws SyntaxException missing proper argument + * @throws InvalidResponseException invalid response + * @throws NonEmptyContainerException container not empty + * @throws NoSuchContainerException remote container does not exist + */ + function delete_container($container=NULL) + { + $container_name = NULL; + + if (is_object($container)) { + if (get_class($container) == "CF_Container") { + $container_name = $container->name; + } + } + if (is_string($container)) { + $container_name = $container; + } + + if ($container_name != "0" and !isset($container_name)) + throw new SyntaxException("Must specify container object or name."); + + $return_code = $this->cfs_http->delete_container($container_name); + + if (!$return_code) { + throw new InvalidResponseException("Failed to obtain http response"); + } + #if ($status == 401 && $this->_re_auth()) { + # return $this->delete_container($container); + #} + if ($return_code == 409) { + throw new NonEmptyContainerException( + "Container must be empty prior to removing it."); + } + if ($return_code == 404) { + throw new NoSuchContainerException( + "Specified container did not exist to delete."); + } + if ($return_code != 204) { + throw new InvalidResponseException( + "Invalid response (".$return_code."): " + . $this->cfs_http->get_error()); + } + return True; + } + + /** + * Return a Container instance + * + * For the given name, return a Container instance if the remote Container + * exists, otherwise throw a Not Found exception. + * + * Example: + * + * # ... authentication code excluded (see previous examples) ... + * # + * $conn = new CF_Authentication($auth); + * + * $images = $conn->get_container("my photos"); + * print "Number of Objects: " . $images->count . "\n"; + * print "Bytes stored in container: " . $images->bytes . "\n"; + * + * + * @param string $container_name name of the remote Container + * @return container CF_Container instance + * @throws NoSuchContainerException thrown if no remote Container + * @throws InvalidResponseException unexpected response + */ + function get_container($container_name=NULL) + { + list($status, $reason, $count, $bytes) = + $this->cfs_http->head_container($container_name); + #if ($status == 401 && $this->_re_auth()) { + # return $this->get_container($container_name); + #} + if ($status == 404) { + throw new NoSuchContainerException("Container not found."); + } + if ($status < 200 || $status > 299) { + throw new InvalidResponseException( + "Invalid response: ".$this->cfs_http->get_error()); + } + return new CF_Container($this->cfs_auth, $this->cfs_http, + $container_name, $count, $bytes); + } + + /** + * Return array of Container instances + * + * Return an array of CF_Container instances on the account. The instances + * will be fully populated with Container attributes (bytes stored and + * Object count) + * + * Example: + * + * # ... authentication code excluded (see previous examples) ... + * # + * $conn = new CF_Authentication($auth); + * + * $clist = $conn->get_containers(); + * foreach ($clist as $cont) { + * print "Container name: " . $cont->name . "\n"; + * print "Number of Objects: " . $cont->count . "\n"; + * print "Bytes stored in container: " . $cont->bytes . "\n"; + * } + * + * + * @return array An array of CF_Container instances + * @throws InvalidResponseException unexpected response + */ + function get_containers($limit=0, $marker=NULL) + { + list($status, $reason, $container_info) = + $this->cfs_http->list_containers_info($limit, $marker); + #if ($status == 401 && $this->_re_auth()) { + # return $this->get_containers(); + #} + if ($status < 200 || $status > 299) { + throw new InvalidResponseException( + "Invalid response: ".$this->cfs_http->get_error()); + } + $containers = array(); + foreach ($container_info as $name => $info) { + $containers[] = new CF_Container($this->cfs_auth, $this->cfs_http, + $info['name'], $info["count"], $info["bytes"], False); + } + return $containers; + } + + /** + * Return list of remote Containers + * + * Return an array of strings containing the names of all remote Containers. + * + * Example: + * + * # ... authentication code excluded (see previous examples) ... + * # + * $conn = new CF_Authentication($auth); + * + * $container_list = $conn->list_containers(); + * print_r($container_list); + * Array + * ( + * [0] => "my photos", + * [1] => "my docs" + * ) + * + * + * @param integer $limit restrict results to $limit Containers + * @param string $marker return results greater than $marker + * @return array list of remote Containers + * @throws InvalidResponseException unexpected response + */ + function list_containers($limit=0, $marker=NULL) + { + list($status, $reason, $containers) = + $this->cfs_http->list_containers($limit, $marker); + #if ($status == 401 && $this->_re_auth()) { + # return $this->list_containers($limit, $marker); + #} + if ($status < 200 || $status > 299) { + throw new InvalidResponseException( + "Invalid response (".$status."): ".$this->cfs_http->get_error()); + } + return $containers; + } + + /** + * Return array of information about remote Containers + * + * Return a nested array structure of Container info. + * + * Example: + * + * # ... authentication code excluded (see previous examples) ... + * # + * + * $container_info = $conn->list_containers_info(); + * print_r($container_info); + * Array + * ( + * ["my photos"] => + * Array + * ( + * ["bytes"] => 78, + * ["count"] => 2 + * ) + * ["docs"] => + * Array + * ( + * ["bytes"] => 37323, + * ["count"] => 12 + * ) + * ) + * + * + * @param integer $limit restrict results to $limit Containers + * @param string $marker return results greater than $marker + * @return array nested array structure of Container info + * @throws InvalidResponseException unexpected response + */ + function list_containers_info($limit=0, $marker=NULL) + { + list($status, $reason, $container_info) = + $this->cfs_http->list_containers_info($limit, $marker); + #if ($status == 401 && $this->_re_auth()) { + # return $this->list_containers_info($limit, $marker); + #} + if ($status < 200 || $status > 299) { + throw new InvalidResponseException( + "Invalid response (".$status."): ".$this->cfs_http->get_error()); + } + return $container_info; + } + + /** + * Return list of Containers that have been published to the CDN. + * + * Return an array of strings containing the names of published Containers. + * Note that this function returns the list of any Container that has + * ever been CDN-enabled regardless of it's existence in the storage + * system. + * + * Example: + * + * # ... authentication code excluded (see previous examples) ... + * # + * $conn = new CF_Authentication($auth); + * + * $public_containers = $conn->list_public_containers(); + * print_r($public_containers); + * Array + * ( + * [0] => "images", + * [1] => "css", + * [2] => "javascript" + * ) + * + * + * @param bool $enabled_only Will list all containers ever CDN enabled if * set to false or only currently enabled CDN containers if set to true. * Defaults to false. + * @return array list of published Container names + * @throws InvalidResponseException unexpected response + */ + function list_public_containers($enabled_only=False) + { + list($status, $reason, $containers) = + $this->cfs_http->list_cdn_containers($enabled_only); + #if ($status == 401 && $this->_re_auth()) { + # return $this->list_public_containers(); + #} + if ($status < 200 || $status > 299) { + throw new InvalidResponseException( + "Invalid response (".$status."): ".$this->cfs_http->get_error()); + } + return $containers; + } + + /** + * Set a user-supplied callback function to report download progress + * + * The callback function is used to report incremental progress of a data + * download functions (e.g. $container->list_objects(), $obj->read(), etc). + * The specified function will be periodically called with the number of + * bytes transferred until the entire download is complete. This callback + * function can be useful for implementing "progress bars" for large + * downloads. + * + * The specified callback function should take a single integer parameter. + * + * + * function read_callback($bytes_transferred) { + * print ">> downloaded " . $bytes_transferred . " bytes.\n"; + * # ... do other things ... + * return; + * } + * + * $conn = new CF_Connection($auth_obj); + * $conn->set_read_progress_function("read_callback"); + * print_r($conn->list_containers()); + * + * # output would look like this: + * # + * >> downloaded 10 bytes. + * >> downloaded 11 bytes. + * Array + * ( + * [0] => fuzzy.txt + * [1] => space name + * ) + * + * + * @param string $func_name the name of the user callback function + */ + function set_read_progress_function($func_name) + { + $this->cfs_http->setReadProgressFunc($func_name); + } + + /** + * Set a user-supplied callback function to report upload progress + * + * The callback function is used to report incremental progress of a data + * upload functions (e.g. $obj->write() call). The specified function will + * be periodically called with the number of bytes transferred until the + * entire upload is complete. This callback function can be useful + * for implementing "progress bars" for large uploads/downloads. + * + * The specified callback function should take a single integer parameter. + * + * + * function write_callback($bytes_transferred) { + * print ">> uploaded " . $bytes_transferred . " bytes.\n"; + * # ... do other things ... + * return; + * } + * + * $conn = new CF_Connection($auth_obj); + * $conn->set_write_progress_function("write_callback"); + * $container = $conn->create_container("stuff"); + * $obj = $container->create_object("foo"); + * $obj->write("The callback function will be called during upload."); + * + * # output would look like this: + * # >> uploaded 51 bytes. + * # + * + * + * @param string $func_name the name of the user callback function + */ + function set_write_progress_function($func_name) + { + $this->cfs_http->setWriteProgressFunc($func_name); + } + + /** + * Use the Certificate Authority bundle included with this API + * + * Most versions of PHP with cURL support include an outdated Certificate + * Authority (CA) bundle (the file that lists all valid certificate + * signing authorities). The SSL certificates used by the Cloud Files + * storage system are perfectly valid but have been created/signed by + * a CA not listed in these outdated cURL distributions. + * + * As a work-around, we've included an updated CA bundle obtained + * directly from cURL's web site (http://curl.haxx.se). You can direct + * the API to use this CA bundle by calling this method prior to making + * any remote calls. The best place to use this method is right after + * the CF_Authentication instance has been instantiated. + * + * You can specify your own CA bundle by passing in the full pathname + * to the bundle. You can use the included CA bundle by leaving the + * argument blank. + * + * @param string $path Specify path to CA bundle (default to included) + */ + function ssl_use_cabundle($path=NULL) + { + $this->cfs_http->ssl_use_cabundle($path); + } + + #private function _re_auth() + #{ + # $new_auth = new CF_Authentication( + # $this->cfs_auth->username, + # $this->cfs_auth->api_key, + # $this->cfs_auth->auth_host, + # $this->cfs_auth->account); + # $new_auth->authenticate(); + # $this->cfs_auth = $new_auth; + # $this->cfs_http->setCFAuth($this->cfs_auth); + # return True; + #} +} + +/** + * Container operations + * + * Containers are storage compartments where you put your data (objects). + * A container is similar to a directory or folder on a conventional filesystem + * with the exception that they exist in a flat namespace, you can not create + * containers inside of containers. + * + * You also have the option of marking a Container as "public" so that the + * Objects stored in the Container are publicly available via the CDN. + * + * @package php-cloudfiles + */ +class CF_Container +{ + public $cfs_auth; + public $cfs_http; + public $name; + public $object_count; + public $bytes_used; + + public $cdn_enabled; + public $cdn_streaming_uri; + public $cdn_ssl_uri; + public $cdn_uri; + public $cdn_ttl; + public $cdn_log_retention; + public $cdn_acl_user_agent; + public $cdn_acl_referrer; + + /** + * Class constructor + * + * Constructor for Container + * + * @param obj $cfs_auth CF_Authentication instance + * @param obj $cfs_http HTTP connection manager + * @param string $name name of Container + * @param int $count number of Objects stored in this Container + * @param int $bytes number of bytes stored in this Container + * @throws SyntaxException invalid Container name + */ + function __construct(&$cfs_auth, &$cfs_http, $name, $count=0, + $bytes=0, $docdn=True) + { + if (strlen($name) > MAX_CONTAINER_NAME_LEN) { + throw new SyntaxException("Container name exceeds " + . "maximum allowed length."); + } + if (strpos($name, "/") !== False) { + throw new SyntaxException( + "Container names cannot contain a '/' character."); + } + $this->cfs_auth = $cfs_auth; + $this->cfs_http = $cfs_http; + $this->name = $name; + $this->object_count = $count; + $this->bytes_used = $bytes; + $this->cdn_enabled = NULL; + $this->cdn_uri = NULL; + $this->cdn_ssl_uri = NULL; + $this->cdn_streaming_uri = NULL; + $this->cdn_ttl = NULL; + $this->cdn_log_retention = NULL; + $this->cdn_acl_user_agent = NULL; + $this->cdn_acl_referrer = NULL; + if ($this->cfs_http->getCDNMUrl() != NULL && $docdn) { + $this->_cdn_initialize(); + } + } + + /** + * String representation of Container + * + * Pretty print the Container instance. + * + * @return string Container details + */ + function __toString() + { + $me = sprintf("name: %s, count: %.0f, bytes: %.0f", + $this->name, $this->object_count, $this->bytes_used); + if ($this->cfs_http->getCDNMUrl() != NULL) { + $me .= sprintf(", cdn: %s, cdn uri: %s, cdn ttl: %.0f, logs retention: %s", + $this->is_public() ? "Yes" : "No", + $this->cdn_uri, $this->cdn_ttl, + $this->cdn_log_retention ? "Yes" : "No" + ); + + if ($this->cdn_acl_user_agent != NULL) { + $me .= ", cdn acl user agent: " . $this->cdn_acl_user_agent; + } + + if ($this->cdn_acl_referrer != NULL) { + $me .= ", cdn acl referrer: " . $this->cdn_acl_referrer; + } + + + } + return $me; + } + + /** + * Enable Container content to be served via CDN or modify CDN attributes + * + * Either enable this Container's content to be served via CDN or + * adjust its CDN attributes. This Container will always return the + * same CDN-enabled URI each time it is toggled public/private/public. + * + * Example: + * + * # ... authentication code excluded (see previous examples) ... + * # + * $conn = new CF_Authentication($auth); + * + * $public_container = $conn->create_container("public"); + * + * # CDN-enable the container and set it's TTL for a month + * # + * $public_container->make_public(86400/2); # 12 hours (86400 seconds/day) + * + * + * @param int $ttl the time in seconds content will be cached in the CDN + * @returns string the CDN enabled Container's URI + * @throws CDNNotEnabledException CDN functionality not returned during auth + * @throws AuthenticationException if auth token is not valid/expired + * @throws InvalidResponseException unexpected response + */ + function make_public($ttl=86400) + { + if ($this->cfs_http->getCDNMUrl() == NULL) { + throw new CDNNotEnabledException( + "Authentication response did not indicate CDN availability"); + } + if ($this->cdn_uri != NULL) { + # previously published, assume we're setting new attributes + list($status, $reason, $cdn_uri, $cdn_ssl_uri) = + $this->cfs_http->update_cdn_container($this->name,$ttl, + $this->cdn_log_retention, + $this->cdn_acl_user_agent, + $this->cdn_acl_referrer); + #if ($status == 401 && $this->_re_auth()) { + # return $this->make_public($ttl); + #} + if ($status == 404) { + # this instance _thinks_ the container was published, but the + # cdn management system thinks otherwise - try again with a PUT + list($status, $reason, $cdn_uri, $cdn_ssl_uri) = + $this->cfs_http->add_cdn_container($this->name,$ttl); + + } + } else { + # publish it for first time + list($status, $reason, $cdn_uri, $cdn_ssl_uri) = + $this->cfs_http->add_cdn_container($this->name,$ttl); + } + #if ($status == 401 && $this->_re_auth()) { + # return $this->make_public($ttl); + #} + if (!in_array($status, array(201,202))) { + throw new InvalidResponseException( + "Invalid response (".$status."): ".$this->cfs_http->get_error()); + } + $this->cdn_enabled = True; + $this->cdn_ttl = $ttl; + $this->cdn_ssl_uri = $cdn_ssl_uri; + $this->cdn_uri = $cdn_uri; + $this->cdn_log_retention = False; + $this->cdn_acl_user_agent = ""; + $this->cdn_acl_referrer = ""; + return $this->cdn_uri; + } + /** + * Purge Containers objects from CDN Cache. + * Example: + * + * # ... authentication code excluded (see previous examples) ... + * # + * $conn = new CF_Authentication($auth); + * $container = $conn->get_container("cdn_enabled"); + * $container->purge_from_cdn("user@domain.com"); + * # or + * $container->purge_from_cdn(); + * # or + * $container->purge_from_cdn("user1@domain.com,user2@domain.com"); + * @returns boolean True if successful + * @throws CDNNotEnabledException if CDN Is not enabled on this connection + * @throws InvalidResponseException if the response expected is not returned + */ + function purge_from_cdn($email=null) + { + if (!$this->cfs_http->getCDNMUrl()) + { + throw new CDNNotEnabledException( + "Authentication response did not indicate CDN availability"); + } + $status = $this->cfs_http->purge_from_cdn($this->name, $email); + if ($status < 199 or $status > 299) { + throw new InvalidResponseException( + "Invalid response (".$status."): ".$this->cfs_http->get_error()); + } + return True; + } + /** + * Enable ACL restriction by User Agent for this container. + * + * Example: + * + * # ... authentication code excluded (see previous examples) ... + * # + * $conn = new CF_Authentication($auth); + * + * $public_container = $conn->get_container("public"); + * + * # Enable ACL by Referrer + * $public_container->acl_referrer("Mozilla"); + * + * + * @returns boolean True if successful + * @throws CDNNotEnabledException CDN functionality not returned during auth + * @throws AuthenticationException if auth token is not valid/expired + * @throws InvalidResponseException unexpected response + */ + function acl_user_agent($cdn_acl_user_agent="") { + if ($this->cfs_http->getCDNMUrl() == NULL) { + throw new CDNNotEnabledException( + "Authentication response did not indicate CDN availability"); + } + list($status,$reason) = + $this->cfs_http->update_cdn_container($this->name, + $this->cdn_ttl, + $this->cdn_log_retention, + $cdn_acl_user_agent, + $this->cdn_acl_referrer + ); + if (!in_array($status, array(202,404))) { + throw new InvalidResponseException( + "Invalid response (".$status."): ".$this->cfs_http->get_error()); + } + $this->cdn_acl_user_agent = $cdn_acl_user_agent; + return True; + } + + /** + * Enable ACL restriction by referer for this container. + * + * Example: + * + * # ... authentication code excluded (see previous examples) ... + * # + * $conn = new CF_Authentication($auth); + * + * $public_container = $conn->get_container("public"); + * + * # Enable Referrer + * $public_container->acl_referrer("http://www.example.com/gallery.php"); + * + * + * @returns boolean True if successful + * @throws CDNNotEnabledException CDN functionality not returned during auth + * @throws AuthenticationException if auth token is not valid/expired + * @throws InvalidResponseException unexpected response + */ + function acl_referrer($cdn_acl_referrer="") { + if ($this->cfs_http->getCDNMUrl() == NULL) { + throw new CDNNotEnabledException( + "Authentication response did not indicate CDN availability"); + } + list($status,$reason) = + $this->cfs_http->update_cdn_container($this->name, + $this->cdn_ttl, + $this->cdn_log_retention, + $this->cdn_acl_user_agent, + $cdn_acl_referrer + ); + if (!in_array($status, array(202,404))) { + throw new InvalidResponseException( + "Invalid response (".$status."): ".$this->cfs_http->get_error()); + } + $this->cdn_acl_referrer = $cdn_acl_referrer; + return True; + } + + /** + * Enable log retention for this CDN container. + * + * Enable CDN log retention on the container. If enabled logs will + * be periodically (at unpredictable intervals) compressed and + * uploaded to a ".CDN_ACCESS_LOGS" container in the form of + * "container_name.YYYYMMDDHH-XXXX.gz". Requires CDN be enabled on + * the account. + * + * Example: + * + * # ... authentication code excluded (see previous examples) ... + * # + * $conn = new CF_Authentication($auth); + * + * $public_container = $conn->get_container("public"); + * + * # Enable logs retention. + * $public_container->log_retention(True); + * + * + * @returns boolean True if successful + * @throws CDNNotEnabledException CDN functionality not returned during auth + * @throws AuthenticationException if auth token is not valid/expired + * @throws InvalidResponseException unexpected response + */ + function log_retention($cdn_log_retention=False) { + if ($this->cfs_http->getCDNMUrl() == NULL) { + throw new CDNNotEnabledException( + "Authentication response did not indicate CDN availability"); + } + list($status,$reason) = + $this->cfs_http->update_cdn_container($this->name, + $this->cdn_ttl, + $cdn_log_retention, + $this->cdn_acl_user_agent, + $this->cdn_acl_referrer + ); + if (!in_array($status, array(202,404))) { + throw new InvalidResponseException( + "Invalid response (".$status."): ".$this->cfs_http->get_error()); + } + $this->cdn_log_retention = $cdn_log_retention; + return True; + } + + /** + * Disable the CDN sharing for this container + * + * Use this method to disallow distribution into the CDN of this Container's + * content. + * + * NOTE: Any content already cached in the CDN will continue to be served + * from its cache until the TTL expiration transpires. The default + * TTL is typically one day, so "privatizing" the Container will take + * up to 24 hours before the content is purged from the CDN cache. + * + * Example: + * + * # ... authentication code excluded (see previous examples) ... + * # + * $conn = new CF_Authentication($auth); + * + * $public_container = $conn->get_container("public"); + * + * # Disable CDN accessability + * # ... still cached up to a month based on previous example + * # + * $public_container->make_private(); + * + * + * @returns boolean True if successful + * @throws CDNNotEnabledException CDN functionality not returned during auth + * @throws AuthenticationException if auth token is not valid/expired + * @throws InvalidResponseException unexpected response + */ + function make_private() + { + if ($this->cfs_http->getCDNMUrl() == NULL) { + throw new CDNNotEnabledException( + "Authentication response did not indicate CDN availability"); + } + list($status,$reason) = $this->cfs_http->remove_cdn_container($this->name); + #if ($status == 401 && $this->_re_auth()) { + # return $this->make_private(); + #} + if (!in_array($status, array(202,404))) { + throw new InvalidResponseException( + "Invalid response (".$status."): ".$this->cfs_http->get_error()); + } + $this->cdn_enabled = False; + $this->cdn_ttl = NULL; + $this->cdn_uri = NULL; + $this->cdn_ssl_uri = NULL; + $this->cdn_streaming_uri - NULL; + $this->cdn_log_retention = NULL; + $this->cdn_acl_user_agent = NULL; + $this->cdn_acl_referrer = NULL; + return True; + } + + /** + * Check if this Container is being publicly served via CDN + * + * Use this method to determine if the Container's content is currently + * available through the CDN. + * + * Example: + * + * # ... authentication code excluded (see previous examples) ... + * # + * $conn = new CF_Authentication($auth); + * + * $public_container = $conn->get_container("public"); + * + * # Display CDN accessability + * # + * $public_container->is_public() ? print "Yes" : print "No"; + * + * + * @returns boolean True if enabled, False otherwise + */ + function is_public() + { + return $this->cdn_enabled == True ? True : False; + } + + /** + * Create a new remote storage Object + * + * Return a new Object instance. If the remote storage Object exists, + * the instance's attributes are populated. + * + * Example: + * + * # ... authentication code excluded (see previous examples) ... + * # + * $conn = new CF_Authentication($auth); + * + * $public_container = $conn->get_container("public"); + * + * # This creates a local instance of a storage object but only creates + * # it in the storage system when the object's write() method is called. + * # + * $pic = $public_container->create_object("baby.jpg"); + * + * + * @param string $obj_name name of storage Object + * @return obj CF_Object instance + */ + function create_object($obj_name=NULL) + { + return new CF_Object($this, $obj_name); + } + + /** + * Return an Object instance for the remote storage Object + * + * Given a name, return a Object instance representing the + * remote storage object. + * + * Example: + * + * # ... authentication code excluded (see previous examples) ... + * # + * $conn = new CF_Authentication($auth); + * + * $public_container = $conn->get_container("public"); + * + * # This call only fetches header information and not the content of + * # the storage object. Use the Object's read() or stream() methods + * # to obtain the object's data. + * # + * $pic = $public_container->get_object("baby.jpg"); + * + * + * @param string $obj_name name of storage Object + * @return obj CF_Object instance + */ + function get_object($obj_name=NULL) + { + return new CF_Object($this, $obj_name, True); + } + + /** + * Return a list of Objects + * + * Return an array of strings listing the Object names in this Container. + * + * Example: + * + * # ... authentication code excluded (see previous examples) ... + * # + * $images = $conn->get_container("my photos"); + * + * # Grab the list of all storage objects + * # + * $all_objects = $images->list_objects(); + * + * # Grab subsets of all storage objects + * # + * $first_ten = $images->list_objects(10); + * + * # Note the use of the previous result's last object name being + * # used as the 'marker' parameter to fetch the next 10 objects + * # + * $next_ten = $images->list_objects(10, $first_ten[count($first_ten)-1]); + * + * # Grab images starting with "birthday_party" and default limit/marker + * # to match all photos with that prefix + * # + * $prefixed = $images->list_objects(0, NULL, "birthday"); + * + * # Assuming you have created the appropriate directory marker Objects, + * # you can traverse your pseudo-hierarchical containers + * # with the "path" argument. + * # + * $animals = $images->list_objects(0,NULL,NULL,"pictures/animals"); + * $dogs = $images->list_objects(0,NULL,NULL,"pictures/animals/dogs"); + * + * + * @param int $limit optional only return $limit names + * @param int $marker optional subset of names starting at $marker + * @param string $prefix optional Objects whose names begin with $prefix + * @param string $path optional only return results under "pathname" + * @return array array of strings + * @throws InvalidResponseException unexpected response + */ + function list_objects($limit=0, $marker=NULL, $prefix=NULL, $path=NULL) + { + list($status, $reason, $obj_list) = + $this->cfs_http->list_objects($this->name, $limit, + $marker, $prefix, $path); + #if ($status == 401 && $this->_re_auth()) { + # return $this->list_objects($limit, $marker, $prefix, $path); + #} + if ($status < 200 || $status > 299) { + throw new InvalidResponseException( + "Invalid response (".$status."): ".$this->cfs_http->get_error()); + } + return $obj_list; + } + + /** + * Return an array of Objects + * + * Return an array of Object instances in this Container. + * + * Example: + * + * # ... authentication code excluded (see previous examples) ... + * # + * $images = $conn->get_container("my photos"); + * + * # Grab the list of all storage objects + * # + * $all_objects = $images->get_objects(); + * + * # Grab subsets of all storage objects + * # + * $first_ten = $images->get_objects(10); + * + * # Note the use of the previous result's last object name being + * # used as the 'marker' parameter to fetch the next 10 objects + * # + * $next_ten = $images->list_objects(10, $first_ten[count($first_ten)-1]); + * + * # Grab images starting with "birthday_party" and default limit/marker + * # to match all photos with that prefix + * # + * $prefixed = $images->get_objects(0, NULL, "birthday"); + * + * # Assuming you have created the appropriate directory marker Objects, + * # you can traverse your pseudo-hierarchical containers + * # with the "path" argument. + * # + * $animals = $images->get_objects(0,NULL,NULL,"pictures/animals"); + * $dogs = $images->get_objects(0,NULL,NULL,"pictures/animals/dogs"); + * + * + * @param int $limit optional only return $limit names + * @param int $marker optional subset of names starting at $marker + * @param string $prefix optional Objects whose names begin with $prefix + * @param string $path optional only return results under "pathname" + * @return array array of strings + * @throws InvalidResponseException unexpected response + */ + function get_objects($limit=0, $marker=NULL, $prefix=NULL, $path=NULL) + { + list($status, $reason, $obj_array) = + $this->cfs_http->get_objects($this->name, $limit, + $marker, $prefix, $path); + #if ($status == 401 && $this->_re_auth()) { + # return $this->get_objects($limit, $marker, $prefix, $path); + #} + if ($status < 200 || $status > 299) { + throw new InvalidResponseException( + "Invalid response (".$status."): ".$this->cfs_http->get_error()); + } + $objects = array(); + foreach ($obj_array as $obj) { + $tmp = new CF_Object($this, $obj["name"], False, False); + $tmp->content_type = $obj["content_type"]; + $tmp->content_length = (float) $obj["bytes"]; + $tmp->set_etag($obj["hash"]); + $tmp->last_modified = $obj["last_modified"]; + $objects[] = $tmp; + } + return $objects; + } + + /** + * Copy a remote storage Object to a target Container + * + * Given an Object instance or name and a target Container instance or name, copy copies the remote Object + * and all associated metadata. + * + * Example: + * + * # ... authentication code excluded (see previous examples) ... + * # + * $conn = new CF_Authentication($auth); + * + * $images = $conn->get_container("my photos"); + * + * # Copy specific object + * # + * $images->copy_object_to("disco_dancing.jpg","container_target"); + * + * + * @param obj $obj name or instance of Object to copy + * @param obj $container_target name or instance of target Container + * @param string $dest_obj_name name of target object (optional - uses source name if omitted) + * @param array $metadata metadata array for new object (optional) + * @param array $headers header fields array for the new object (optional) + * @return boolean true if successfully copied + * @throws SyntaxException invalid Object/Container name + * @throws NoSuchObjectException remote Object does not exist + * @throws InvalidResponseException unexpected response + */ + function copy_object_to($obj,$container_target,$dest_obj_name=NULL,$metadata=NULL,$headers=NULL) + { + $obj_name = NULL; + if (is_object($obj)) { + if (get_class($obj) == "CF_Object") { + $obj_name = $obj->name; + } + } + if (is_string($obj)) { + $obj_name = $obj; + } + if (!$obj_name) { + throw new SyntaxException("Object name not set."); + } + + if ($dest_obj_name === NULL) { + $dest_obj_name = $obj_name; + } + + $container_name_target = NULL; + if (is_object($container_target)) { + if (get_class($container_target) == "CF_Container") { + $container_name_target = $container_target->name; + } + } + if (is_string($container_target)) { + $container_name_target = $container_target; + } + if (!$container_name_target) { + throw new SyntaxException("Container name target not set."); + } + + $status = $this->cfs_http->copy_object($obj_name,$dest_obj_name,$this->name,$container_name_target,$metadata,$headers); + if ($status == 404) { + $m = "Specified object '".$this->name."/".$obj_name; + $m.= "' did not exist as source to copy from or '".$container_name_target."' did not exist as target to copy to."; + throw new NoSuchObjectException($m); + } + if ($status < 200 || $status > 299) { + throw new InvalidResponseException( + "Invalid response (".$status."): ".$this->cfs_http->get_error()); + } + return true; + } + + /** + * Copy a remote storage Object from a source Container + * + * Given an Object instance or name and a source Container instance or name, copy copies the remote Object + * and all associated metadata. + * + * Example: + * + * # ... authentication code excluded (see previous examples) ... + * # + * $conn = new CF_Authentication($auth); + * + * $images = $conn->get_container("my photos"); + * + * # Copy specific object + * # + * $images->copy_object_from("disco_dancing.jpg","container_source"); + * + * + * @param obj $obj name or instance of Object to copy + * @param obj $container_source name or instance of source Container + * @param string $dest_obj_name name of target object (optional - uses source name if omitted) + * @param array $metadata metadata array for new object (optional) + * @param array $headers header fields array for the new object (optional) + * @return boolean true if successfully copied + * @throws SyntaxException invalid Object/Container name + * @throws NoSuchObjectException remote Object does not exist + * @throws InvalidResponseException unexpected response + */ + function copy_object_from($obj,$container_source,$dest_obj_name=NULL,$metadata=NULL,$headers=NULL) + { + $obj_name = NULL; + if (is_object($obj)) { + if (get_class($obj) == "CF_Object") { + $obj_name = $obj->name; + } + } + if (is_string($obj)) { + $obj_name = $obj; + } + if (!$obj_name) { + throw new SyntaxException("Object name not set."); + } + + if ($dest_obj_name === NULL) { + $dest_obj_name = $obj_name; + } + + $container_name_source = NULL; + if (is_object($container_source)) { + if (get_class($container_source) == "CF_Container") { + $container_name_source = $container_source->name; + } + } + if (is_string($container_source)) { + $container_name_source = $container_source; + } + if (!$container_name_source) { + throw new SyntaxException("Container name source not set."); + } + + $status = $this->cfs_http->copy_object($obj_name,$dest_obj_name,$container_name_source,$this->name,$metadata,$headers); + if ($status == 404) { + $m = "Specified object '".$container_name_source."/".$obj_name; + $m.= "' did not exist as source to copy from or '".$this->name."/".$obj_name."' did not exist as target to copy to."; + throw new NoSuchObjectException($m); + } + if ($status < 200 || $status > 299) { + throw new InvalidResponseException( + "Invalid response (".$status."): ".$this->cfs_http->get_error()); + } + + return true; + } + + /** + * Move a remote storage Object to a target Container + * + * Given an Object instance or name and a target Container instance or name, move copies the remote Object + * and all associated metadata and deletes the source Object afterwards + * + * Example: + * + * # ... authentication code excluded (see previous examples) ... + * # + * $conn = new CF_Authentication($auth); + * + * $images = $conn->get_container("my photos"); + * + * # Move specific object + * # + * $images->move_object_to("disco_dancing.jpg","container_target"); + * + * + * @param obj $obj name or instance of Object to move + * @param obj $container_target name or instance of target Container + * @param string $dest_obj_name name of target object (optional - uses source name if omitted) + * @param array $metadata metadata array for new object (optional) + * @param array $headers header fields array for the new object (optional) + * @return boolean true if successfully moved + * @throws SyntaxException invalid Object/Container name + * @throws NoSuchObjectException remote Object does not exist + * @throws InvalidResponseException unexpected response + */ + function move_object_to($obj,$container_target,$dest_obj_name=NULL,$metadata=NULL,$headers=NULL) + { + $retVal = false; + + if(self::copy_object_to($obj,$container_target,$dest_obj_name,$metadata,$headers)) { + $retVal = self::delete_object($obj,$this->name); + } + + return $retVal; + } + + /** + * Move a remote storage Object from a source Container + * + * Given an Object instance or name and a source Container instance or name, move copies the remote Object + * and all associated metadata and deletes the source Object afterwards + * + * Example: + * + * # ... authentication code excluded (see previous examples) ... + * # + * $conn = new CF_Authentication($auth); + * + * $images = $conn->get_container("my photos"); + * + * # Move specific object + * # + * $images->move_object_from("disco_dancing.jpg","container_target"); + * + * + * @param obj $obj name or instance of Object to move + * @param obj $container_source name or instance of target Container + * @param string $dest_obj_name name of target object (optional - uses source name if omitted) + * @param array $metadata metadata array for new object (optional) + * @param array $headers header fields array for the new object (optional) + * @return boolean true if successfully moved + * @throws SyntaxException invalid Object/Container name + * @throws NoSuchObjectException remote Object does not exist + * @throws InvalidResponseException unexpected response + */ + function move_object_from($obj,$container_source,$dest_obj_name=NULL,$metadata=NULL,$headers=NULL) + { + $retVal = false; + + if(self::copy_object_from($obj,$container_source,$dest_obj_name,$metadata,$headers)) { + $retVal = self::delete_object($obj,$container_source); + } + + return $retVal; + } + + /** + * Delete a remote storage Object + * + * Given an Object instance or name, permanently remove the remote Object + * and all associated metadata. + * + * Example: + * + * # ... authentication code excluded (see previous examples) ... + * # + * $conn = new CF_Authentication($auth); + * + * $images = $conn->get_container("my photos"); + * + * # Delete specific object + * # + * $images->delete_object("disco_dancing.jpg"); + * + * + * @param obj $obj name or instance of Object to delete + * @param obj $container name or instance of Container in which the object resides (optional) + * @return boolean True if successfully removed + * @throws SyntaxException invalid Object name + * @throws NoSuchObjectException remote Object does not exist + * @throws InvalidResponseException unexpected response + */ + function delete_object($obj,$container=NULL) + { + $obj_name = NULL; + if (is_object($obj)) { + if (get_class($obj) == "CF_Object") { + $obj_name = $obj->name; + } + } + if (is_string($obj)) { + $obj_name = $obj; + } + if (!$obj_name) { + throw new SyntaxException("Object name not set."); + } + + $container_name = NULL; + + if($container === NULL) { + $container_name = $this->name; + } + else { + if (is_object($container)) { + if (get_class($container) == "CF_Container") { + $container_name = $container->name; + } + } + if (is_string($container)) { + $container_name = $container; + } + if (!$container_name) { + throw new SyntaxException("Container name source not set."); + } + } + + $status = $this->cfs_http->delete_object($container_name, $obj_name); + #if ($status == 401 && $this->_re_auth()) { + # return $this->delete_object($obj); + #} + if ($status == 404) { + $m = "Specified object '".$container_name."/".$obj_name; + $m.= "' did not exist to delete."; + throw new NoSuchObjectException($m); + } + if ($status != 204) { + throw new InvalidResponseException( + "Invalid response (".$status."): ".$this->cfs_http->get_error()); + } + return True; + } + + /** + * Helper function to create "path" elements for a given Object name + * + * Given an Object whos name contains '/' path separators, this function + * will create the "directory marker" Objects of one byte with the + * Content-Type of "application/directory". + * + * It assumes the last element of the full path is the "real" Object + * and does NOT create a remote storage Object for that last element. + */ + function create_paths($path_name) + { + if ($path_name[0] == '/') { + $path_name = mb_substr($path_name, 0, 1); + } + $elements = explode('/', $path_name, -1); + $build_path = ""; + foreach ($elements as $idx => $val) { + if (!$build_path) { + $build_path = $val; + } else { + $build_path .= "/" . $val; + } + $obj = new CF_Object($this, $build_path); + $obj->content_type = "application/directory"; + $obj->write(".", 1); + } + } + + /** + * Internal method to grab CDN/Container info if appropriate to do so + * + * @throws InvalidResponseException unexpected response + */ + private function _cdn_initialize() + { + list($status, $reason, $cdn_enabled, $cdn_ssl_uri, $cdn_streaming_uri, $cdn_uri, $cdn_ttl, + $cdn_log_retention, $cdn_acl_user_agent, $cdn_acl_referrer) = + $this->cfs_http->head_cdn_container($this->name); + #if ($status == 401 && $this->_re_auth()) { + # return $this->_cdn_initialize(); + #} + if (!in_array($status, array(204,404))) { + throw new InvalidResponseException( + "Invalid response (".$status."): ".$this->cfs_http->get_error()); + } + $this->cdn_enabled = $cdn_enabled; + $this->cdn_streaming_uri = $cdn_streaming_uri; + $this->cdn_ssl_uri = $cdn_ssl_uri; + $this->cdn_uri = $cdn_uri; + $this->cdn_ttl = $cdn_ttl; + $this->cdn_log_retention = $cdn_log_retention; + $this->cdn_acl_user_agent = $cdn_acl_user_agent; + $this->cdn_acl_referrer = $cdn_acl_referrer; + } + + #private function _re_auth() + #{ + # $new_auth = new CF_Authentication( + # $this->cfs_auth->username, + # $this->cfs_auth->api_key, + # $this->cfs_auth->auth_host, + # $this->cfs_auth->account); + # $new_auth->authenticate(); + # $this->cfs_auth = $new_auth; + # $this->cfs_http->setCFAuth($this->cfs_auth); + # return True; + #} +} + + +/** + * Object operations + * + * An Object is analogous to a file on a conventional filesystem. You can + * read data from, or write data to your Objects. You can also associate + * arbitrary metadata with them. + * + * @package php-cloudfiles + */ +class CF_Object +{ + public $container; + public $name; + public $last_modified; + public $content_type; + public $content_length; + public $metadata; + public $headers; + public $manifest; + private $etag; + + /** + * Class constructor + * + * @param obj $container CF_Container instance + * @param string $name name of Object + * @param boolean $force_exists if set, throw an error if Object doesn't exist + */ + function __construct(&$container, $name, $force_exists=False, $dohead=True) + { + if ($name[0] == "/") { + $r = "Object name '".$name; + $r .= "' cannot contain begin with a '/' character."; + throw new SyntaxException($r); + } + if (strlen($name) > MAX_OBJECT_NAME_LEN) { + throw new SyntaxException("Object name exceeds " + . "maximum allowed length."); + } + $this->container = $container; + $this->name = $name; + $this->etag = NULL; + $this->_etag_override = False; + $this->last_modified = NULL; + $this->content_type = NULL; + $this->content_length = 0; + $this->metadata = array(); + $this->headers = array(); + $this->manifest = NULL; + if ($dohead) { + if (!$this->_initialize() && $force_exists) { + throw new NoSuchObjectException("No such object '".$name."'"); + } + } + } + + /** + * String representation of Object + * + * Pretty print the Object's location and name + * + * @return string Object information + */ + function __toString() + { + return $this->container->name . "/" . $this->name; + } + + /** + * Internal check to get the proper mimetype. + * + * This function would go over the available PHP methods to get + * the MIME type. + * + * By default it will try to use the PHP fileinfo library which is + * available from PHP 5.3 or as an PECL extension + * (http://pecl.php.net/package/Fileinfo). + * + * It will get the magic file by default from the system wide file + * which is usually available in /usr/share/magic on Unix or try + * to use the file specified in the source directory of the API + * (share directory). + * + * if fileinfo is not available it will try to use the internal + * mime_content_type function. + * + * @param string $handle name of file or buffer to guess the type from + * @return boolean True if successful + * @throws BadContentTypeException + */ + function _guess_content_type($handle) { + if ($this->content_type) + return; + +// if (function_exists("finfo_open")) { +// $local_magic = dirname(__FILE__) . "/share/magic"; +// $finfo = @finfo_open(FILEINFO_MIME, $local_magic); +// +// if (!$finfo) +// $finfo = @finfo_open(FILEINFO_MIME); +// +// if ($finfo) { +// +// if (is_file((string)$handle)) +// $ct = @finfo_file($finfo, $handle); +// else +// $ct = @finfo_buffer($finfo, $handle); +// +// /* PHP 5.3 fileinfo display extra information like +// charset so we remove everything after the ; since +// we are not into that stuff */ +// if ($ct) { +// $extra_content_type_info = strpos($ct, "; "); +// if ($extra_content_type_info) +// $ct = substr($ct, 0, $extra_content_type_info); +// } +// +// if ($ct && $ct != 'application/octet-stream') +// $this->content_type = $ct; +// +// @finfo_close($finfo); +// } +// } +// +// if (!$this->content_type && (string)is_file($handle) && function_exists("mime_content_type")) { +// $this->content_type = @mime_content_type($handle); +// } + + //use OC's mimetype detection for files + if(is_file($handle)){ + $this->content_type=OC_Helper::getMimeType($handle); + }else{ + $this->content_type=OC_Helper::getStringMimeType($handle); + } + + if (!$this->content_type) { + throw new BadContentTypeException("Required Content-Type not set"); + } + return True; + } + + /** + * String representation of the Object's public URI + * + * A string representing the Object's public URI assuming that it's + * parent Container is CDN-enabled. + * + * Example: + * + * # ... authentication/connection/container code excluded + * # ... see previous examples + * + * # Print out the Object's CDN URI (if it has one) in an HTML img-tag + * # + * print "\n"; + * + * + * @return string Object's public URI or NULL + */ + function public_uri() + { + if ($this->container->cdn_enabled) { + return $this->container->cdn_uri . "/" . $this->name; + } + return NULL; + } + + /** + * String representation of the Object's public SSL URI + * + * A string representing the Object's public SSL URI assuming that it's + * parent Container is CDN-enabled. + * + * Example: + * + * # ... authentication/connection/container code excluded + * # ... see previous examples + * + * # Print out the Object's CDN SSL URI (if it has one) in an HTML img-tag + * # + * print "\n"; + * + * + * @return string Object's public SSL URI or NULL + */ + function public_ssl_uri() + { + if ($this->container->cdn_enabled) { + return $this->container->cdn_ssl_uri . "/" . $this->name; + } + return NULL; + } + /** + * String representation of the Object's public Streaming URI + * + * A string representing the Object's public Streaming URI assuming that it's + * parent Container is CDN-enabled. + * + * Example: + * + * # ... authentication/connection/container code excluded + * # ... see previous examples + * + * # Print out the Object's CDN Streaming URI (if it has one) in an HTML img-tag + * # + * print "\n"; + * + * + * @return string Object's public Streaming URI or NULL + */ + function public_streaming_uri() + { + if ($this->container->cdn_enabled) { + return $this->container->cdn_streaming_uri . "/" . $this->name; + } + return NULL; + } + + /** + * Read the remote Object's data + * + * Returns the Object's data. This is useful for smaller Objects such + * as images or office documents. Object's with larger content should use + * the stream() method below. + * + * Pass in $hdrs array to set specific custom HTTP headers such as + * If-Match, If-None-Match, If-Modified-Since, Range, etc. + * + * Example: + * + * # ... authentication/connection/container code excluded + * # ... see previous examples + * + * $my_docs = $conn->get_container("documents"); + * $doc = $my_docs->get_object("README"); + * $data = $doc->read(); # read image content into a string variable + * print $data; + * + * # Or see stream() below for a different example. + * # + * + * + * @param array $hdrs user-defined headers (Range, If-Match, etc.) + * @return string Object's data + * @throws InvalidResponseException unexpected response + */ + function read($hdrs=array()) + { + list($status, $reason, $data) = + $this->container->cfs_http->get_object_to_string($this, $hdrs); + #if ($status == 401 && $this->_re_auth()) { + # return $this->read($hdrs); + #} + if (($status < 200) || ($status > 299 + && $status != 412 && $status != 304)) { + throw new InvalidResponseException("Invalid response (".$status."): " + . $this->container->cfs_http->get_error()); + } + return $data; + } + + /** + * Streaming read of Object's data + * + * Given an open PHP resource (see PHP's fopen() method), fetch the Object's + * data and write it to the open resource handle. This is useful for + * streaming an Object's content to the browser (videos, images) or for + * fetching content to a local file. + * + * Pass in $hdrs array to set specific custom HTTP headers such as + * If-Match, If-None-Match, If-Modified-Since, Range, etc. + * + * Example: + * + * # ... authentication/connection/container code excluded + * # ... see previous examples + * + * # Assuming this is a web script to display the README to the + * # user's browser: + * # + * get_container("documents"); + * $doc = $my_docs->get_object("README"); + * + * // Hand it back to user's browser with appropriate content-type + * // + * header("Content-Type: " . $doc->content_type); + * $output = fopen("php://output", "w"); + * $doc->stream($output); # stream object content to PHP's output buffer + * fclose($output); + * ?> + * + * # See read() above for a more simple example. + * # + * + * + * @param resource $fp open resource for writing data to + * @param array $hdrs user-defined headers (Range, If-Match, etc.) + * @return string Object's data + * @throws InvalidResponseException unexpected response + */ + function stream(&$fp, $hdrs=array()) + { + list($status, $reason) = + $this->container->cfs_http->get_object_to_stream($this,$fp,$hdrs); + #if ($status == 401 && $this->_re_auth()) { + # return $this->stream($fp, $hdrs); + #} + if (($status < 200) || ($status > 299 + && $status != 412 && $status != 304)) { + throw new InvalidResponseException("Invalid response (".$status."): " + .$reason); + } + return True; + } + + /** + * Store new Object metadata + * + * Write's an Object's metadata to the remote Object. This will overwrite + * an prior Object metadata. + * + * Example: + * + * # ... authentication/connection/container code excluded + * # ... see previous examples + * + * $my_docs = $conn->get_container("documents"); + * $doc = $my_docs->get_object("README"); + * + * # Define new metadata for the object + * # + * $doc->metadata = array( + * "Author" => "EJ", + * "Subject" => "How to use the PHP tests", + * "Version" => "1.2.2" + * ); + * + * # Define additional headers for the object + * # + * $doc->headers = array( + * "Content-Disposition" => "attachment", + * ); + * + * # Push the new metadata up to the storage system + * # + * $doc->sync_metadata(); + * + * + * @return boolean True if successful, False otherwise + * @throws InvalidResponseException unexpected response + */ + function sync_metadata() + { + if (!empty($this->metadata) || !empty($this->headers) || $this->manifest) { + $status = $this->container->cfs_http->update_object($this); + #if ($status == 401 && $this->_re_auth()) { + # return $this->sync_metadata(); + #} + if ($status != 202) { + throw new InvalidResponseException("Invalid response (" + .$status."): ".$this->container->cfs_http->get_error()); + } + return True; + } + return False; + } + /** + * Store new Object manifest + * + * Write's an Object's manifest to the remote Object. This will overwrite + * an prior Object manifest. + * + * Example: + * + * # ... authentication/connection/container code excluded + * # ... see previous examples + * + * $my_docs = $conn->get_container("documents"); + * $doc = $my_docs->get_object("README"); + * + * # Define new manifest for the object + * # + * $doc->manifest = "container/prefix"; + * + * # Push the new manifest up to the storage system + * # + * $doc->sync_manifest(); + * + * + * @return boolean True if successful, False otherwise + * @throws InvalidResponseException unexpected response + */ + + function sync_manifest() + { + return $this->sync_metadata(); + } + /** + * Upload Object's data to Cloud Files + * + * Write data to the remote Object. The $data argument can either be a + * PHP resource open for reading (see PHP's fopen() method) or an in-memory + * variable. If passing in a PHP resource, you must also include the $bytes + * parameter. + * + * Example: + * + * # ... authentication/connection/container code excluded + * # ... see previous examples + * + * $my_docs = $conn->get_container("documents"); + * $doc = $my_docs->get_object("README"); + * + * # Upload placeholder text in my README + * # + * $doc->write("This is just placeholder text for now..."); + * + * + * @param string|resource $data string or open resource + * @param float $bytes amount of data to upload (required for resources) + * @param boolean $verify generate, send, and compare MD5 checksums + * @return boolean True when data uploaded successfully + * @throws SyntaxException missing required parameters + * @throws BadContentTypeException if no Content-Type was/could be set + * @throws MisMatchedChecksumException $verify is set and checksums unequal + * @throws InvalidResponseException unexpected response + */ + function write($data=NULL, $bytes=0, $verify=True) + { + if (!$data && !is_string($data)) { + throw new SyntaxException("Missing data source."); + } + if ($bytes > MAX_OBJECT_SIZE) { + throw new SyntaxException("Bytes exceeds maximum object size."); + } + if ($verify) { + if (!$this->_etag_override) { + $this->etag = $this->compute_md5sum($data); + } + } else { + $this->etag = NULL; + } + + $close_fh = False; + if (!is_resource($data)) { + # A hack to treat string data as a file handle. php://memory feels + # like a better option, but it seems to break on Windows so use + # a temporary file instead. + # + $fp = fopen("php://temp", "wb+"); + #$fp = fopen("php://memory", "wb+"); + fwrite($fp, $data, strlen($data)); + rewind($fp); + $close_fh = True; + $this->content_length = (float) strlen($data); + if ($this->content_length > MAX_OBJECT_SIZE) { + throw new SyntaxException("Data exceeds maximum object size"); + } + $ct_data = substr($data, 0, 64); + } else { + $this->content_length = $bytes; + $fp = $data; + $ct_data = fread($data, 64); + rewind($data); + } + + $this->_guess_content_type($ct_data); + + list($status, $reason, $etag) = + $this->container->cfs_http->put_object($this, $fp); + #if ($status == 401 && $this->_re_auth()) { + # return $this->write($data, $bytes, $verify); + #} + if ($status == 412) { + if ($close_fh) { fclose($fp); } + throw new SyntaxException("Missing Content-Type header"); + } + if ($status == 422) { + if ($close_fh) { fclose($fp); } + throw new MisMatchedChecksumException( + "Supplied and computed checksums do not match."); + } + if ($status != 201) { + if ($close_fh) { fclose($fp); } + throw new InvalidResponseException("Invalid response (".$status."): " + . $this->container->cfs_http->get_error()); + } + if (!$verify) { + $this->etag = $etag; + } + if ($close_fh) { fclose($fp); } + return True; + } + + /** + * Upload Object data from local filename + * + * This is a convenience function to upload the data from a local file. A + * True value for $verify will cause the method to compute the Object's MD5 + * checksum prior to uploading. + * + * Example: + * + * # ... authentication/connection/container code excluded + * # ... see previous examples + * + * $my_docs = $conn->get_container("documents"); + * $doc = $my_docs->get_object("README"); + * + * # Upload my local README's content + * # + * $doc->load_from_filename("/home/ej/cloudfiles/readme"); + * + * + * @param string $filename full path to local file + * @param boolean $verify enable local/remote MD5 checksum validation + * @return boolean True if data uploaded successfully + * @throws SyntaxException missing required parameters + * @throws BadContentTypeException if no Content-Type was/could be set + * @throws MisMatchedChecksumException $verify is set and checksums unequal + * @throws InvalidResponseException unexpected response + * @throws IOException error opening file + */ + function load_from_filename($filename, $verify=True) + { + $fp = @fopen($filename, "r"); + if (!$fp) { + throw new IOException("Could not open file for reading: ".$filename); + } + + clearstatcache(); + + $size = (float) sprintf("%u", filesize($filename)); + if ($size > MAX_OBJECT_SIZE) { + throw new SyntaxException("File size exceeds maximum object size."); + } + + $this->_guess_content_type($filename); + + $this->write($fp, $size, $verify); + fclose($fp); + return True; + } + + /** + * Save Object's data to local filename + * + * Given a local filename, the Object's data will be written to the newly + * created file. + * + * Example: + * + * # ... authentication/connection/container code excluded + * # ... see previous examples + * + * # Whoops! I deleted my local README, let me download/save it + * # + * $my_docs = $conn->get_container("documents"); + * $doc = $my_docs->get_object("README"); + * + * $doc->save_to_filename("/home/ej/cloudfiles/readme.restored"); + * + * + * @param string $filename name of local file to write data to + * @return boolean True if successful + * @throws IOException error opening file + * @throws InvalidResponseException unexpected response + */ + function save_to_filename($filename) + { + $fp = @fopen($filename, "wb"); + if (!$fp) { + throw new IOException("Could not open file for writing: ".$filename); + } + $result = $this->stream($fp); + fclose($fp); + return $result; + } + /** + * Purge this Object from CDN Cache. + * Example: + * + * # ... authentication code excluded (see previous examples) ... + * # + * $conn = new CF_Authentication($auth); + * $container = $conn->get_container("cdn_enabled"); + * $obj = $container->get_object("object"); + * $obj->purge_from_cdn("user@domain.com"); + * # or + * $obj->purge_from_cdn(); + * # or + * $obj->purge_from_cdn("user1@domain.com,user2@domain.com"); + * @returns boolean True if successful + * @throws CDNNotEnabledException if CDN Is not enabled on this connection + * @throws InvalidResponseException if the response expected is not returned + */ + function purge_from_cdn($email=null) + { + if (!$this->container->cfs_http->getCDNMUrl()) + { + throw new CDNNotEnabledException( + "Authentication response did not indicate CDN availability"); + } + $status = $this->container->cfs_http->purge_from_cdn($this->container->name . "/" . $this->name, $email); + if ($status < 199 or $status > 299) { + throw new InvalidResponseException( + "Invalid response (".$status."): ".$this->container->cfs_http->get_error()); + } + return True; + } + + /** + * Set Object's MD5 checksum + * + * Manually set the Object's ETag. Including the ETag is mandatory for + * Cloud Files to perform end-to-end verification. Omitting the ETag forces + * the user to handle any data integrity checks. + * + * @param string $etag MD5 checksum hexidecimal string + */ + function set_etag($etag) + { + $this->etag = $etag; + $this->_etag_override = True; + } + + /** + * Object's MD5 checksum + * + * Accessor method for reading Object's private ETag attribute. + * + * @return string MD5 checksum hexidecimal string + */ + function getETag() + { + return $this->etag; + } + + /** + * Compute the MD5 checksum + * + * Calculate the MD5 checksum on either a PHP resource or data. The argument + * may either be a local filename, open resource for reading, or a string. + * + * WARNING: if you are uploading a big file over a stream + * it could get very slow to compute the md5 you probably want to + * set the $verify parameter to False in the write() method and + * compute yourself the md5 before if you have it. + * + * @param filename|obj|string $data filename, open resource, or string + * @return string MD5 checksum hexidecimal string + */ + function compute_md5sum(&$data) + { + + if (function_exists("hash_init") && is_resource($data)) { + $ctx = hash_init('md5'); + while (!feof($data)) { + $buffer = fgets($data, 65536); + hash_update($ctx, $buffer); + } + $md5 = hash_final($ctx, false); + rewind($data); + } elseif ((string)is_file($data)) { + $md5 = md5_file($data); + } else { + $md5 = md5($data); + } + return $md5; + } + + /** + * PRIVATE: fetch information about the remote Object if it exists + */ + private function _initialize() + { + list($status, $reason, $etag, $last_modified, $content_type, + $content_length, $metadata, $manifest, $headers) = + $this->container->cfs_http->head_object($this); + #if ($status == 401 && $this->_re_auth()) { + # return $this->_initialize(); + #} + if ($status == 404) { + return False; + } + if ($status < 200 || $status > 299) { + throw new InvalidResponseException("Invalid response (".$status."): " + . $this->container->cfs_http->get_error()); + } + $this->etag = $etag; + $this->last_modified = $last_modified; + $this->content_type = $content_type; + $this->content_length = $content_length; + $this->metadata = $metadata; + $this->headers = $headers; + $this->manifest = $manifest; + return True; + } + + #private function _re_auth() + #{ + # $new_auth = new CF_Authentication( + # $this->cfs_auth->username, + # $this->cfs_auth->api_key, + # $this->cfs_auth->auth_host, + # $this->cfs_auth->account); + # $new_auth->authenticate(); + # $this->container->cfs_auth = $new_auth; + # $this->container->cfs_http->setCFAuth($this->cfs_auth); + # return True; + #} +} + +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * c-hanging-comment-ender-p: nil + * End: + */ +?> diff --git a/3rdparty/php-cloudfiles/cloudfiles_exceptions.php b/3rdparty/php-cloudfiles/cloudfiles_exceptions.php new file mode 100644 index 0000000000..5624d6b863 --- /dev/null +++ b/3rdparty/php-cloudfiles/cloudfiles_exceptions.php @@ -0,0 +1,41 @@ + + * @copyright Copyright (c) 2008, Rackspace US, Inc. + * @package php-cloudfiles-exceptions + */ + +/** + * Custom Exceptions for the CloudFiles API + * @package php-cloudfiles-exceptions + */ +class SyntaxException extends Exception { } +class AuthenticationException extends Exception { } +class InvalidResponseException extends Exception { } +class NonEmptyContainerException extends Exception { } +class NoSuchObjectException extends Exception { } +class NoSuchContainerException extends Exception { } +class NoSuchAccountException extends Exception { } +class MisMatchedChecksumException extends Exception { } +class IOException extends Exception { } +class CDNNotEnabledException extends Exception { } +class BadContentTypeException extends Exception { } +class InvalidUTF8Exception extends Exception { } +class ConnectionNotOpenException extends Exception { } + +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * c-hanging-comment-ender-p: nil + * End: + */ +?> diff --git a/3rdparty/php-cloudfiles/cloudfiles_http.php b/3rdparty/php-cloudfiles/cloudfiles_http.php new file mode 100644 index 0000000000..0e5d9717e8 --- /dev/null +++ b/3rdparty/php-cloudfiles/cloudfiles_http.php @@ -0,0 +1,1488 @@ + + * @copyright Copyright (c) 2008, Rackspace US, Inc. + * @package php-cloudfiles-http + */ + +/** + */ +require_once("cloudfiles_exceptions.php"); + +define("PHP_CF_VERSION", "1.7.10"); +define("USER_AGENT", sprintf("PHP-CloudFiles/%s", PHP_CF_VERSION)); +define("MAX_HEADER_NAME_LEN", 128); +define("MAX_HEADER_VALUE_LEN", 256); +define("ACCOUNT_CONTAINER_COUNT", "X-Account-Container-Count"); +define("ACCOUNT_BYTES_USED", "X-Account-Bytes-Used"); +define("CONTAINER_OBJ_COUNT", "X-Container-Object-Count"); +define("CONTAINER_BYTES_USED", "X-Container-Bytes-Used"); +define("MANIFEST_HEADER", "X-Object-Manifest"); +define("METADATA_HEADER_PREFIX", "X-Object-Meta-"); +define("CONTENT_HEADER_PREFIX", "Content-"); +define("ACCESS_CONTROL_HEADER_PREFIX", "Access-Control-"); +define("ORIGIN_HEADER", "Origin"); +define("CDN_URI", "X-CDN-URI"); +define("CDN_SSL_URI", "X-CDN-SSL-URI"); +define("CDN_STREAMING_URI", "X-CDN-Streaming-URI"); +define("CDN_ENABLED", "X-CDN-Enabled"); +define("CDN_LOG_RETENTION", "X-Log-Retention"); +define("CDN_ACL_USER_AGENT", "X-User-Agent-ACL"); +define("CDN_ACL_REFERRER", "X-Referrer-ACL"); +define("CDN_TTL", "X-TTL"); +define("CDNM_URL", "X-CDN-Management-Url"); +define("STORAGE_URL", "X-Storage-Url"); +define("AUTH_TOKEN", "X-Auth-Token"); +define("AUTH_USER_HEADER", "X-Auth-User"); +define("AUTH_KEY_HEADER", "X-Auth-Key"); +define("AUTH_USER_HEADER_LEGACY", "X-Storage-User"); +define("AUTH_KEY_HEADER_LEGACY", "X-Storage-Pass"); +define("AUTH_TOKEN_LEGACY", "X-Storage-Token"); +define("CDN_EMAIL", "X-Purge-Email"); +define("DESTINATION", "Destination"); +define("ETAG_HEADER", "ETag"); +define("LAST_MODIFIED_HEADER", "Last-Modified"); +define("CONTENT_TYPE_HEADER", "Content-Type"); +define("CONTENT_LENGTH_HEADER", "Content-Length"); +define("USER_AGENT_HEADER", "User-Agent"); + +/** + * HTTP/cURL wrapper for Cloud Files + * + * This class should not be used directly. It's only purpose is to abstract + * out the HTTP communication from the main API. + * + * @package php-cloudfiles-http + */ +class CF_Http +{ + private $error_str; + private $dbug; + private $cabundle_path; + private $api_version; + + # Authentication instance variables + # + private $storage_url; + private $cdnm_url; + private $auth_token; + + # Request/response variables + # + private $response_status; + private $response_reason; + private $connections; + + # Variables used for content/header callbacks + # + private $_user_read_progress_callback_func; + private $_user_write_progress_callback_func; + private $_write_callback_type; + private $_text_list; + private $_account_container_count; + private $_account_bytes_used; + private $_container_object_count; + private $_container_bytes_used; + private $_obj_etag; + private $_obj_last_modified; + private $_obj_content_type; + private $_obj_content_length; + private $_obj_metadata; + private $_obj_headers; + private $_obj_manifest; + private $_obj_write_resource; + private $_obj_write_string; + private $_cdn_enabled; + private $_cdn_ssl_uri; + private $_cdn_streaming_uri; + private $_cdn_uri; + private $_cdn_ttl; + private $_cdn_log_retention; + private $_cdn_acl_user_agent; + private $_cdn_acl_referrer; + + function __construct($api_version) + { + $this->dbug = False; + $this->cabundle_path = NULL; + $this->api_version = $api_version; + $this->error_str = NULL; + + $this->storage_url = NULL; + $this->cdnm_url = NULL; + $this->auth_token = NULL; + + $this->response_status = NULL; + $this->response_reason = NULL; + + # Curl connections array - since there is no way to "re-set" the + # connection paramaters for a cURL handle, we keep an array of + # the unique use-cases and funnel all of those same type + # requests through the appropriate curl connection. + # + $this->connections = array( + "GET_CALL" => NULL, # GET objects/containers/lists + "PUT_OBJ" => NULL, # PUT object + "HEAD" => NULL, # HEAD requests + "PUT_CONT" => NULL, # PUT container + "DEL_POST" => NULL, # DELETE containers/objects, POST objects + "COPY" => null, # COPY objects + ); + + $this->_user_read_progress_callback_func = NULL; + $this->_user_write_progress_callback_func = NULL; + $this->_write_callback_type = NULL; + $this->_text_list = array(); + $this->_return_list = NULL; + $this->_account_container_count = 0; + $this->_account_bytes_used = 0; + $this->_container_object_count = 0; + $this->_container_bytes_used = 0; + $this->_obj_write_resource = NULL; + $this->_obj_write_string = ""; + $this->_obj_etag = NULL; + $this->_obj_last_modified = NULL; + $this->_obj_content_type = NULL; + $this->_obj_content_length = NULL; + $this->_obj_metadata = array(); + $this->_obj_manifest = NULL; + $this->_obj_headers = NULL; + $this->_cdn_enabled = NULL; + $this->_cdn_ssl_uri = NULL; + $this->_cdn_streaming_uri = NULL; + $this->_cdn_uri = NULL; + $this->_cdn_ttl = NULL; + $this->_cdn_log_retention = NULL; + $this->_cdn_acl_user_agent = NULL; + $this->_cdn_acl_referrer = NULL; + + # The OS list with a PHP without an updated CA File for CURL to + # connect to SSL Websites. It is the first 3 letters of the PHP_OS + # variable. + $OS_CAFILE_NONUPDATED=array( + "win","dar" + ); + + if (in_array((strtolower (substr(PHP_OS, 0,3))), $OS_CAFILE_NONUPDATED)) + $this->ssl_use_cabundle(); + + } + + function ssl_use_cabundle($path=NULL) + { + if ($path) { + $this->cabundle_path = $path; + } else { + $this->cabundle_path = dirname(__FILE__) . "/share/cacert.pem"; + } + if (!file_exists($this->cabundle_path)) { + throw new IOException("Could not use CA bundle: " + . $this->cabundle_path); + } + return; + } + + # Uses separate cURL connection to authenticate + # + function authenticate($user, $pass, $acct=NULL, $host=NULL) + { + $path = array(); + if (isset($acct)){ + $headers = array( + sprintf("%s: %s", AUTH_USER_HEADER_LEGACY, $user), + sprintf("%s: %s", AUTH_KEY_HEADER_LEGACY, $pass), + ); + $path[] = $host; + $path[] = rawurlencode(sprintf("v%d",$this->api_version)); + $path[] = rawurlencode($acct); + } else { + $headers = array( + sprintf("%s: %s", AUTH_USER_HEADER, $user), + sprintf("%s: %s", AUTH_KEY_HEADER, $pass), + ); + $path[] = $host; + } + $path[] = "v1.0"; + $url = implode("/", $path); + + $curl_ch = curl_init(); + if (!is_null($this->cabundle_path)) { + curl_setopt($curl_ch, CURLOPT_SSL_VERIFYPEER, True); + curl_setopt($curl_ch, CURLOPT_CAINFO, $this->cabundle_path); + } + curl_setopt($curl_ch, CURLOPT_VERBOSE, $this->dbug); + curl_setopt($curl_ch, CURLOPT_FOLLOWLOCATION, 1); + curl_setopt($curl_ch, CURLOPT_MAXREDIRS, 4); + curl_setopt($curl_ch, CURLOPT_HEADER, 0); + curl_setopt($curl_ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($curl_ch, CURLOPT_USERAGENT, USER_AGENT); + curl_setopt($curl_ch, CURLOPT_RETURNTRANSFER, TRUE); + curl_setopt($curl_ch, CURLOPT_HEADERFUNCTION,array(&$this,'_auth_hdr_cb')); + curl_setopt($curl_ch, CURLOPT_CONNECTTIMEOUT, 10); + curl_setopt($curl_ch, CURLOPT_URL, $url); + curl_exec($curl_ch); + curl_close($curl_ch); + + return array($this->response_status, $this->response_reason, + $this->storage_url, $this->cdnm_url, $this->auth_token); + } + + # (CDN) GET /v1/Account + # + function list_cdn_containers($enabled_only) + { + $conn_type = "GET_CALL"; + $url_path = $this->_make_path("CDN"); + + $this->_write_callback_type = "TEXT_LIST"; + if ($enabled_only) + { + $return_code = $this->_send_request($conn_type, $url_path . + '/?enabled_only=true'); + } + else + { + $return_code = $this->_send_request($conn_type, $url_path); + } + if (!$return_code) { + $this->error_str .= ": Failed to obtain valid HTTP response."; + return array(0,$this->error_str,array()); + } + if ($return_code == 401) { + return array($return_code,"Unauthorized",array()); + } + if ($return_code == 404) { + return array($return_code,"Account not found.",array()); + } + if ($return_code == 204) { + return array($return_code,"Account has no CDN enabled Containers.", + array()); + } + if ($return_code == 200) { + $this->create_array(); + return array($return_code,$this->response_reason,$this->_text_list); + } + $this->error_str = "Unexpected HTTP response: ".$this->response_reason; + return array($return_code,$this->error_str,array()); + } + + # (CDN) DELETE /v1/Account/Container or /v1/Account/Container/Object + # + function purge_from_cdn($path, $email=null) + { + if(!$path) + throw new SyntaxException("Path not set"); + $url_path = $this->_make_path("CDN", NULL, $path); + if($email) + { + $hdrs = array(CDN_EMAIL => $email); + $return_code = $this->_send_request("DEL_POST",$url_path,$hdrs,"DELETE"); + } + else + $return_code = $this->_send_request("DEL_POST",$url_path,null,"DELETE"); + return $return_code; + } + + # (CDN) POST /v1/Account/Container + function update_cdn_container($container_name, $ttl=86400, $cdn_log_retention=False, + $cdn_acl_user_agent="", $cdn_acl_referrer) + { + if ($container_name == "") + throw new SyntaxException("Container name not set."); + + if ($container_name != "0" and !isset($container_name)) + throw new SyntaxException("Container name not set."); + + $url_path = $this->_make_path("CDN", $container_name); + $hdrs = array( + CDN_ENABLED => "True", + CDN_TTL => $ttl, + CDN_LOG_RETENTION => $cdn_log_retention ? "True" : "False", + CDN_ACL_USER_AGENT => $cdn_acl_user_agent, + CDN_ACL_REFERRER => $cdn_acl_referrer, + ); + $return_code = $this->_send_request("DEL_POST",$url_path,$hdrs,"POST"); + if ($return_code == 401) { + $this->error_str = "Unauthorized"; + return array($return_code, $this->error_str, NULL); + } + if ($return_code == 404) { + $this->error_str = "Container not found."; + return array($return_code, $this->error_str, NULL); + } + if ($return_code != 202) { + $this->error_str="Unexpected HTTP response: ".$this->response_reason; + return array($return_code, $this->error_str, NULL); + } + return array($return_code, "Accepted", $this->_cdn_uri, $this->_cdn_ssl_uri); + + } + + # (CDN) PUT /v1/Account/Container + # + function add_cdn_container($container_name, $ttl=86400) + { + if ($container_name == "") + throw new SyntaxException("Container name not set."); + + if ($container_name != "0" and !isset($container_name)) + throw new SyntaxException("Container name not set."); + + $url_path = $this->_make_path("CDN", $container_name); + $hdrs = array( + CDN_ENABLED => "True", + CDN_TTL => $ttl, + ); + $return_code = $this->_send_request("PUT_CONT", $url_path, $hdrs); + if ($return_code == 401) { + $this->error_str = "Unauthorized"; + return array($return_code,$this->response_reason,False); + } + if (!in_array($return_code, array(201,202))) { + $this->error_str="Unexpected HTTP response: ".$this->response_reason; + return array($return_code,$this->response_reason,False); + } + return array($return_code,$this->response_reason,$this->_cdn_uri, + $this->_cdn_ssl_uri); + } + + # (CDN) POST /v1/Account/Container + # + function remove_cdn_container($container_name) + { + if ($container_name == "") + throw new SyntaxException("Container name not set."); + + if ($container_name != "0" and !isset($container_name)) + throw new SyntaxException("Container name not set."); + + $url_path = $this->_make_path("CDN", $container_name); + $hdrs = array(CDN_ENABLED => "False"); + $return_code = $this->_send_request("DEL_POST",$url_path,$hdrs,"POST"); + if ($return_code == 401) { + $this->error_str = "Unauthorized"; + return array($return_code, $this->error_str); + } + if ($return_code == 404) { + $this->error_str = "Container not found."; + return array($return_code, $this->error_str); + } + if ($return_code != 202) { + $this->error_str="Unexpected HTTP response: ".$this->response_reason; + return array($return_code, $this->error_str); + } + return array($return_code, "Accepted"); + } + + # (CDN) HEAD /v1/Account + # + function head_cdn_container($container_name) + { + if ($container_name == "") + throw new SyntaxException("Container name not set."); + + if ($container_name != "0" and !isset($container_name)) + throw new SyntaxException("Container name not set."); + + $conn_type = "HEAD"; + $url_path = $this->_make_path("CDN", $container_name); + $return_code = $this->_send_request($conn_type, $url_path, NULL, "GET", True); + + if (!$return_code) { + $this->error_str .= ": Failed to obtain valid HTTP response."; + return array(0,$this->error_str,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); + } + if ($return_code == 401) { + return array($return_code,"Unauthorized",NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); + } + if ($return_code == 404) { + return array($return_code,"Account not found.",NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); + } + if ($return_code == 204) { + return array($return_code,$this->response_reason, + $this->_cdn_enabled, $this->_cdn_ssl_uri, + $this->_cdn_streaming_uri, + $this->_cdn_uri, $this->_cdn_ttl, + $this->_cdn_log_retention, + $this->_cdn_acl_user_agent, + $this->_cdn_acl_referrer + ); + } + return array($return_code,$this->response_reason, + NULL,NULL,NULL,NULL, + $this->_cdn_log_retention, + $this->_cdn_acl_user_agent, + $this->_cdn_acl_referrer, + NULL + ); + } + + # GET /v1/Account + # + function list_containers($limit=0, $marker=NULL) + { + $conn_type = "GET_CALL"; + $url_path = $this->_make_path(); + + $limit = intval($limit); + $params = array(); + if ($limit > 0) { + $params[] = "limit=$limit"; + } + if ($marker) { + $params[] = "marker=".rawurlencode($marker); + } + if (!empty($params)) { + $url_path .= "?" . implode("&", $params); + } + + $this->_write_callback_type = "TEXT_LIST"; + $return_code = $this->_send_request($conn_type, $url_path); + + if (!$return_code) { + $this->error_str .= ": Failed to obtain valid HTTP response."; + return array(0,$this->error_str,array()); + } + if ($return_code == 204) { + return array($return_code, "Account has no containers.", array()); + } + if ($return_code == 404) { + $this->error_str = "Invalid account name for authentication token."; + return array($return_code,$this->error_str,array()); + } + if ($return_code == 200) { + $this->create_array(); + return array($return_code, $this->response_reason, $this->_text_list); + } + $this->error_str = "Unexpected HTTP response: ".$this->response_reason; + return array($return_code,$this->error_str,array()); + } + + # GET /v1/Account?format=json + # + function list_containers_info($limit=0, $marker=NULL) + { + $conn_type = "GET_CALL"; + $url_path = $this->_make_path() . "?format=json"; + + $limit = intval($limit); + $params = array(); + if ($limit > 0) { + $params[] = "limit=$limit"; + } + if ($marker) { + $params[] = "marker=".rawurlencode($marker); + } + if (!empty($params)) { + $url_path .= "&" . implode("&", $params); + } + + $this->_write_callback_type = "OBJECT_STRING"; + $return_code = $this->_send_request($conn_type, $url_path); + + if (!$return_code) { + $this->error_str .= ": Failed to obtain valid HTTP response."; + return array(0,$this->error_str,array()); + } + if ($return_code == 204) { + return array($return_code, "Account has no containers.", array()); + } + if ($return_code == 404) { + $this->error_str = "Invalid account name for authentication token."; + return array($return_code,$this->error_str,array()); + } + if ($return_code == 200) { + $json_body = json_decode($this->_obj_write_string, True); + return array($return_code, $this->response_reason, $json_body); + } + $this->error_str = "Unexpected HTTP response: ".$this->response_reason; + return array($return_code,$this->error_str,array()); + } + + # HEAD /v1/Account + # + function head_account() + { + $conn_type = "HEAD"; + + $url_path = $this->_make_path(); + $return_code = $this->_send_request($conn_type,$url_path); + + if (!$return_code) { + $this->error_str .= ": Failed to obtain valid HTTP response."; + return array(0,$this->error_str,0,0); + } + if ($return_code == 404) { + return array($return_code,"Account not found.",0,0); + } + if ($return_code == 204) { + return array($return_code,$this->response_reason, + $this->_account_container_count, $this->_account_bytes_used); + } + return array($return_code,$this->response_reason,0,0); + } + + # PUT /v1/Account/Container + # + function create_container($container_name) + { + if ($container_name == "") + throw new SyntaxException("Container name not set."); + + if ($container_name != "0" and !isset($container_name)) + throw new SyntaxException("Container name not set."); + + $url_path = $this->_make_path("STORAGE", $container_name); + $return_code = $this->_send_request("PUT_CONT",$url_path); + + if (!$return_code) { + $this->error_str .= ": Failed to obtain valid HTTP response."; + return False; + } + return $return_code; + } + + # DELETE /v1/Account/Container + # + function delete_container($container_name) + { + if ($container_name == "") + throw new SyntaxException("Container name not set."); + + if ($container_name != "0" and !isset($container_name)) + throw new SyntaxException("Container name not set."); + + $url_path = $this->_make_path("STORAGE", $container_name); + $return_code = $this->_send_request("DEL_POST",$url_path,array(),"DELETE"); + + switch ($return_code) { + case 204: + break; + case 0: + $this->error_str .= ": Failed to obtain valid HTTP response.";; + break; + case 409: + $this->error_str = "Container must be empty prior to removing it."; + break; + case 404: + $this->error_str = "Specified container did not exist to delete."; + break; + default: + $this->error_str = "Unexpected HTTP return code: $return_code."; + } + return $return_code; + } + + # GET /v1/Account/Container + # + function list_objects($cname,$limit=0,$marker=NULL,$prefix=NULL,$path=NULL) + { + if (!$cname) { + $this->error_str = "Container name not set."; + return array(0, $this->error_str, array()); + } + + $url_path = $this->_make_path("STORAGE", $cname); + + $limit = intval($limit); + $params = array(); + if ($limit > 0) { + $params[] = "limit=$limit"; + } + if ($marker) { + $params[] = "marker=".rawurlencode($marker); + } + if ($prefix) { + $params[] = "prefix=".rawurlencode($prefix); + } + if ($path) { + $params[] = "path=".rawurlencode($path); + } + if (!empty($params)) { + $url_path .= "?" . implode("&", $params); + } + + $conn_type = "GET_CALL"; + $this->_write_callback_type = "TEXT_LIST"; + $return_code = $this->_send_request($conn_type,$url_path); + + if (!$return_code) { + $this->error_str .= ": Failed to obtain valid HTTP response."; + return array(0,$this->error_str,array()); + } + if ($return_code == 204) { + $this->error_str = "Container has no Objects."; + return array($return_code,$this->error_str,array()); + } + if ($return_code == 404) { + $this->error_str = "Container has no Objects."; + return array($return_code,$this->error_str,array()); + } + if ($return_code == 200) { + $this->create_array(); + return array($return_code,$this->response_reason, $this->_text_list); + } + $this->error_str = "Unexpected HTTP response code: $return_code"; + return array(0,$this->error_str,array()); + } + + # GET /v1/Account/Container?format=json + # + function get_objects($cname,$limit=0,$marker=NULL,$prefix=NULL,$path=NULL) + { + if (!$cname) { + $this->error_str = "Container name not set."; + return array(0, $this->error_str, array()); + } + + $url_path = $this->_make_path("STORAGE", $cname); + + $limit = intval($limit); + $params = array(); + $params[] = "format=json"; + if ($limit > 0) { + $params[] = "limit=$limit"; + } + if ($marker) { + $params[] = "marker=".rawurlencode($marker); + } + if ($prefix) { + $params[] = "prefix=".rawurlencode($prefix); + } + if ($path) { + $params[] = "path=".rawurlencode($path); + } + if (!empty($params)) { + $url_path .= "?" . implode("&", $params); + } + + $conn_type = "GET_CALL"; + $this->_write_callback_type = "OBJECT_STRING"; + $return_code = $this->_send_request($conn_type,$url_path); + + if (!$return_code) { + $this->error_str .= ": Failed to obtain valid HTTP response."; + return array(0,$this->error_str,array()); + } + if ($return_code == 204) { + $this->error_str = "Container has no Objects."; + return array($return_code,$this->error_str,array()); + } + if ($return_code == 404) { + $this->error_str = "Container has no Objects."; + return array($return_code,$this->error_str,array()); + } + if ($return_code == 200) { + $json_body = json_decode($this->_obj_write_string, True); + return array($return_code,$this->response_reason, $json_body); + } + $this->error_str = "Unexpected HTTP response code: $return_code"; + return array(0,$this->error_str,array()); + } + + + # HEAD /v1/Account/Container + # + function head_container($container_name) + { + + if ($container_name == "") { + $this->error_str = "Container name not set."; + return False; + } + + if ($container_name != "0" and !isset($container_name)) { + $this->error_str = "Container name not set."; + return False; + } + + $conn_type = "HEAD"; + + $url_path = $this->_make_path("STORAGE", $container_name); + $return_code = $this->_send_request($conn_type,$url_path); + + if (!$return_code) { + $this->error_str .= ": Failed to obtain valid HTTP response."; + return array(0,$this->error_str,0,0); + } + if ($return_code == 404) { + return array($return_code,"Container not found.",0,0); + } + if ($return_code == 204 || $return_code == 200) { + return array($return_code,$this->response_reason, + $this->_container_object_count, $this->_container_bytes_used); + } + return array($return_code,$this->response_reason,0,0); + } + + # GET /v1/Account/Container/Object + # + function get_object_to_string(&$obj, $hdrs=array()) + { + if (!is_object($obj) || get_class($obj) != "CF_Object") { + throw new SyntaxException( + "Method argument is not a valid CF_Object."); + } + + $conn_type = "GET_CALL"; + + $url_path = $this->_make_path("STORAGE", $obj->container->name,$obj->name); + $this->_write_callback_type = "OBJECT_STRING"; + $return_code = $this->_send_request($conn_type,$url_path,$hdrs); + + if (!$return_code) { + $this->error_str .= ": Failed to obtain valid HTTP response."; + return array($return_code0,$this->error_str,NULL); + } + if ($return_code == 404) { + $this->error_str = "Object not found."; + return array($return_code0,$this->error_str,NULL); + } + if (($return_code < 200) || ($return_code > 299 + && $return_code != 412 && $return_code != 304)) { + $this->error_str = "Unexpected HTTP return code: $return_code"; + return array($return_code,$this->error_str,NULL); + } + return array($return_code,$this->response_reason, $this->_obj_write_string); + } + + # GET /v1/Account/Container/Object + # + function get_object_to_stream(&$obj, &$resource=NULL, $hdrs=array()) + { + if (!is_object($obj) || get_class($obj) != "CF_Object") { + throw new SyntaxException( + "Method argument is not a valid CF_Object."); + } + if (!is_resource($resource)) { + throw new SyntaxException( + "Resource argument not a valid PHP resource."); + } + + $conn_type = "GET_CALL"; + + $url_path = $this->_make_path("STORAGE", $obj->container->name,$obj->name); + $this->_obj_write_resource = $resource; + $this->_write_callback_type = "OBJECT_STREAM"; + $return_code = $this->_send_request($conn_type,$url_path,$hdrs); + + if (!$return_code) { + $this->error_str .= ": Failed to obtain valid HTTP response."; + return array($return_code,$this->error_str); + } + if ($return_code == 404) { + $this->error_str = "Object not found."; + return array($return_code,$this->error_str); + } + if (($return_code < 200) || ($return_code > 299 + && $return_code != 412 && $return_code != 304)) { + $this->error_str = "Unexpected HTTP return code: $return_code"; + return array($return_code,$this->error_str); + } + return array($return_code,$this->response_reason); + } + + # PUT /v1/Account/Container/Object + # + function put_object(&$obj, &$fp) + { + if (!is_object($obj) || get_class($obj) != "CF_Object") { + throw new SyntaxException( + "Method argument is not a valid CF_Object."); + } + if (!is_resource($fp)) { + throw new SyntaxException( + "File pointer argument is not a valid resource."); + } + + $conn_type = "PUT_OBJ"; + $url_path = $this->_make_path("STORAGE", $obj->container->name,$obj->name); + + $hdrs = $this->_headers($obj); + + $etag = $obj->getETag(); + if (isset($etag)) { + $hdrs[] = "ETag: " . $etag; + } + if (!$obj->content_type) { + $hdrs[] = "Content-Type: application/octet-stream"; + } else { + $hdrs[] = "Content-Type: " . $obj->content_type; + } + + $this->_init($conn_type); + curl_setopt($this->connections[$conn_type], + CURLOPT_INFILE, $fp); + if (!$obj->content_length) { + # We don''t know the Content-Length, so assumed "chunked" PUT + # + curl_setopt($this->connections[$conn_type], CURLOPT_UPLOAD, True); + $hdrs[] = 'Transfer-Encoding: chunked'; + } else { + # We know the Content-Length, so use regular transfer + # + curl_setopt($this->connections[$conn_type], + CURLOPT_INFILESIZE, $obj->content_length); + } + $return_code = $this->_send_request($conn_type,$url_path,$hdrs); + + if (!$return_code) { + $this->error_str .= ": Failed to obtain valid HTTP response."; + return array(0,$this->error_str,NULL); + } + if ($return_code == 412) { + $this->error_str = "Missing Content-Type header"; + return array($return_code,$this->error_str,NULL); + } + if ($return_code == 422) { + $this->error_str = "Derived and computed checksums do not match."; + return array($return_code,$this->error_str,NULL); + } + if ($return_code != 201) { + $this->error_str = "Unexpected HTTP return code: $return_code"; + return array($return_code,$this->error_str,NULL); + } + return array($return_code,$this->response_reason,$this->_obj_etag); + } + + # POST /v1/Account/Container/Object + # + function update_object(&$obj) + { + if (!is_object($obj) || get_class($obj) != "CF_Object") { + throw new SyntaxException( + "Method argument is not a valid CF_Object."); + } + + # TODO: The is_array check isn't in sync with the error message + if (!$obj->manifest && !(is_array($obj->metadata) || is_array($obj->headers))) { + $this->error_str = "Metadata and headers arrays are empty."; + return 0; + } + + $url_path = $this->_make_path("STORAGE", $obj->container->name,$obj->name); + + $hdrs = $this->_headers($obj); + $return_code = $this->_send_request("DEL_POST",$url_path,$hdrs,"POST"); + switch ($return_code) { + case 202: + break; + case 0: + $this->error_str .= ": Failed to obtain valid HTTP response."; + $return_code = 0; + break; + case 404: + $this->error_str = "Account, Container, or Object not found."; + break; + default: + $this->error_str = "Unexpected HTTP return code: $return_code"; + break; + } + return $return_code; + } + + # HEAD /v1/Account/Container/Object + # + function head_object(&$obj) + { + if (!is_object($obj) || get_class($obj) != "CF_Object") { + throw new SyntaxException( + "Method argument is not a valid CF_Object."); + } + + $conn_type = "HEAD"; + + $url_path = $this->_make_path("STORAGE", $obj->container->name,$obj->name); + $return_code = $this->_send_request($conn_type,$url_path); + + if (!$return_code) { + $this->error_str .= ": Failed to obtain valid HTTP response."; + return array(0, $this->error_str." ".$this->response_reason, + NULL, NULL, NULL, NULL, array(), NULL, array()); + } + + if ($return_code == 404) { + return array($return_code, $this->response_reason, + NULL, NULL, NULL, NULL, array(), NULL, array()); + } + if ($return_code == 204 || $return_code == 200) { + return array($return_code,$this->response_reason, + $this->_obj_etag, + $this->_obj_last_modified, + $this->_obj_content_type, + $this->_obj_content_length, + $this->_obj_metadata, + $this->_obj_manifest, + $this->_obj_headers); + } + $this->error_str = "Unexpected HTTP return code: $return_code"; + return array($return_code, $this->error_str." ".$this->response_reason, + NULL, NULL, NULL, NULL, array(), NULL, array()); + } + + # COPY /v1/Account/Container/Object + # + function copy_object($src_obj_name, $dest_obj_name, $container_name_source, $container_name_target, $metadata=NULL, $headers=NULL) + { + if (!$src_obj_name) { + $this->error_str = "Object name not set."; + return 0; + } + + if ($container_name_source == "") { + $this->error_str = "Container name source not set."; + return 0; + } + + if ($container_name_source != "0" and !isset($container_name_source)) { + $this->error_str = "Container name source not set."; + return 0; + } + + if ($container_name_target == "") { + $this->error_str = "Container name target not set."; + return 0; + } + + if ($container_name_target != "0" and !isset($container_name_target)) { + $this->error_str = "Container name target not set."; + return 0; + } + + $conn_type = "COPY"; + + $url_path = $this->_make_path("STORAGE", $container_name_source, rawurlencode($src_obj_name)); + $destination = rawurlencode($container_name_target."/".$dest_obj_name); + + $hdrs = self::_process_headers($metadata, $headers); + $hdrs[DESTINATION] = $destination; + + $return_code = $this->_send_request($conn_type,$url_path,$hdrs,"COPY"); + switch ($return_code) { + case 201: + break; + case 0: + $this->error_str .= ": Failed to obtain valid HTTP response."; + $return_code = 0; + break; + case 404: + $this->error_str = "Specified container/object did not exist."; + break; + default: + $this->error_str = "Unexpected HTTP return code: $return_code."; + } + return $return_code; + } + + # DELETE /v1/Account/Container/Object + # + function delete_object($container_name, $object_name) + { + if ($container_name == "") { + $this->error_str = "Container name not set."; + return 0; + } + + if ($container_name != "0" and !isset($container_name)) { + $this->error_str = "Container name not set."; + return 0; + } + + if (!$object_name) { + $this->error_str = "Object name not set."; + return 0; + } + + $url_path = $this->_make_path("STORAGE", $container_name,$object_name); + $return_code = $this->_send_request("DEL_POST",$url_path,NULL,"DELETE"); + switch ($return_code) { + case 204: + break; + case 0: + $this->error_str .= ": Failed to obtain valid HTTP response."; + $return_code = 0; + break; + case 404: + $this->error_str = "Specified container did not exist to delete."; + break; + default: + $this->error_str = "Unexpected HTTP return code: $return_code."; + } + return $return_code; + } + + function get_error() + { + return $this->error_str; + } + + function setDebug($bool) + { + $this->dbug = $bool; + foreach ($this->connections as $k => $v) { + if (!is_null($v)) { + curl_setopt($this->connections[$k], CURLOPT_VERBOSE, $this->dbug); + } + } + } + + function getCDNMUrl() + { + return $this->cdnm_url; + } + + function getStorageUrl() + { + return $this->storage_url; + } + + function getAuthToken() + { + return $this->auth_token; + } + + function setCFAuth($cfs_auth, $servicenet=False) + { + if ($servicenet) { + $this->storage_url = "https://snet-" . substr($cfs_auth->storage_url, 8); + } else { + $this->storage_url = $cfs_auth->storage_url; + } + $this->auth_token = $cfs_auth->auth_token; + $this->cdnm_url = $cfs_auth->cdnm_url; + } + + function setReadProgressFunc($func_name) + { + $this->_user_read_progress_callback_func = $func_name; + } + + function setWriteProgressFunc($func_name) + { + $this->_user_write_progress_callback_func = $func_name; + } + + private function _header_cb($ch, $header) + { + $header_len = strlen($header); + + if (preg_match("/^(HTTP\/1\.[01]) (\d{3}) (.*)/", $header, $matches)) { + $this->response_status = $matches[2]; + $this->response_reason = $matches[3]; + return $header_len; + } + + if (strpos($header, ":") === False) + return $header_len; + list($name, $value) = explode(":", $header, 2); + $value = trim($value); + + switch (strtolower($name)) { + case strtolower(CDN_ENABLED): + $this->_cdn_enabled = strtolower($value) == "true"; + break; + case strtolower(CDN_URI): + $this->_cdn_uri = $value; + break; + case strtolower(CDN_SSL_URI): + $this->_cdn_ssl_uri = $value; + break; + case strtolower(CDN_STREAMING_URI): + $this->_cdn_streaming_uri = $value; + break; + case strtolower(CDN_TTL): + $this->_cdn_ttl = $value; + break; + case strtolower(MANIFEST_HEADER): + $this->_obj_manifest = $value; + break; + case strtolower(CDN_LOG_RETENTION): + $this->_cdn_log_retention = strtolower($value) == "true"; + break; + case strtolower(CDN_ACL_USER_AGENT): + $this->_cdn_acl_user_agent = $value; + break; + case strtolower(CDN_ACL_REFERRER): + $this->_cdn_acl_referrer = $value; + break; + case strtolower(ACCOUNT_CONTAINER_COUNT): + $this->_account_container_count = (float)$value+0; + break; + case strtolower(ACCOUNT_BYTES_USED): + $this->_account_bytes_used = (float)$value+0; + break; + case strtolower(CONTAINER_OBJ_COUNT): + $this->_container_object_count = (float)$value+0; + break; + case strtolower(CONTAINER_BYTES_USED): + $this->_container_bytes_used = (float)$value+0; + break; + case strtolower(ETAG_HEADER): + $this->_obj_etag = $value; + break; + case strtolower(LAST_MODIFIED_HEADER): + $this->_obj_last_modified = $value; + break; + case strtolower(CONTENT_TYPE_HEADER): + $this->_obj_content_type = $value; + break; + case strtolower(CONTENT_LENGTH_HEADER): + $this->_obj_content_length = (float)$value+0; + break; + case strtolower(ORIGIN_HEADER): + $this->_obj_headers[ORIGIN_HEADER] = $value; + break; + default: + if (strncasecmp($name, METADATA_HEADER_PREFIX, strlen(METADATA_HEADER_PREFIX)) == 0) { + $name = substr($name, strlen(METADATA_HEADER_PREFIX)); + $this->_obj_metadata[$name] = $value; + } + elseif ((strncasecmp($name, CONTENT_HEADER_PREFIX, strlen(CONTENT_HEADER_PREFIX)) == 0) || + (strncasecmp($name, ACCESS_CONTROL_HEADER_PREFIX, strlen(ACCESS_CONTROL_HEADER_PREFIX)) == 0)) { + $this->_obj_headers[$name] = $value; + } + } + return $header_len; + } + + private function _read_cb($ch, $fd, $length) + { + $data = fread($fd, $length); + $len = strlen($data); + if (isset($this->_user_write_progress_callback_func)) { + call_user_func($this->_user_write_progress_callback_func, $len); + } + return $data; + } + + private function _write_cb($ch, $data) + { + $dlen = strlen($data); + switch ($this->_write_callback_type) { + case "TEXT_LIST": + $this->_return_list = $this->_return_list . $data; + //= explode("\n",$data); # keep tab,space + //his->_text_list[] = rtrim($data,"\n\r\x0B"); # keep tab,space + break; + case "OBJECT_STREAM": + fwrite($this->_obj_write_resource, $data, $dlen); + break; + case "OBJECT_STRING": + $this->_obj_write_string .= $data; + break; + } + if (isset($this->_user_read_progress_callback_func)) { + call_user_func($this->_user_read_progress_callback_func, $dlen); + } + return $dlen; + } + + private function _auth_hdr_cb($ch, $header) + { + preg_match("/^HTTP\/1\.[01] (\d{3}) (.*)/", $header, $matches); + if (isset($matches[1])) { + $this->response_status = $matches[1]; + } + if (isset($matches[2])) { + $this->response_reason = $matches[2]; + } + if (stripos($header, STORAGE_URL) === 0) { + $this->storage_url = trim(substr($header, strlen(STORAGE_URL)+1)); + } + if (stripos($header, CDNM_URL) === 0) { + $this->cdnm_url = trim(substr($header, strlen(CDNM_URL)+1)); + } + if (stripos($header, AUTH_TOKEN) === 0) { + $this->auth_token = trim(substr($header, strlen(AUTH_TOKEN)+1)); + } + if (stripos($header, AUTH_TOKEN_LEGACY) === 0) { + $this->auth_token = trim(substr($header,strlen(AUTH_TOKEN_LEGACY)+1)); + } + return strlen($header); + } + + private function _make_headers($hdrs=NULL) + { + $new_headers = array(); + $has_stoken = False; + $has_uagent = False; + if (is_array($hdrs)) { + foreach ($hdrs as $h => $v) { + if (is_int($h)) { + list($h, $v) = explode(":", $v, 2); + } + + if (strncasecmp($h, AUTH_TOKEN, strlen(AUTH_TOKEN)) === 0) { + $has_stoken = True; + } + if (strncasecmp($h, USER_AGENT_HEADER, strlen(USER_AGENT_HEADER)) === 0) { + $has_uagent = True; + } + $new_headers[] = $h . ": " . trim($v); + } + } + if (!$has_stoken) { + $new_headers[] = AUTH_TOKEN . ": " . $this->auth_token; + } + if (!$has_uagent) { + $new_headers[] = USER_AGENT_HEADER . ": " . USER_AGENT; + } + return $new_headers; + } + + private function _init($conn_type, $force_new=False) + { + if (!array_key_exists($conn_type, $this->connections)) { + $this->error_str = "Invalid CURL_XXX connection type"; + return False; + } + + if (is_null($this->connections[$conn_type]) || $force_new) { + $ch = curl_init(); + } else { + return; + } + + if ($this->dbug) { curl_setopt($ch, CURLOPT_VERBOSE, 1); } + + if (!is_null($this->cabundle_path)) { + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, True); + curl_setopt($ch, CURLOPT_CAINFO, $this->cabundle_path); + } + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, True); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); + curl_setopt($ch, CURLOPT_MAXREDIRS, 4); + curl_setopt($ch, CURLOPT_HEADER, 0); + curl_setopt($ch, CURLOPT_HEADERFUNCTION, array(&$this, '_header_cb')); + + if ($conn_type == "GET_CALL") { + curl_setopt($ch, CURLOPT_WRITEFUNCTION, array(&$this, '_write_cb')); + } + + if ($conn_type == "PUT_OBJ") { + curl_setopt($ch, CURLOPT_PUT, 1); + curl_setopt($ch, CURLOPT_READFUNCTION, array(&$this, '_read_cb')); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + } + if ($conn_type == "HEAD") { + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "HEAD"); + curl_setopt($ch, CURLOPT_NOBODY, 1); + } + if ($conn_type == "PUT_CONT") { + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PUT"); + curl_setopt($ch, CURLOPT_INFILESIZE, 0); + curl_setopt($ch, CURLOPT_NOBODY, 1); + } + if ($conn_type == "DEL_POST") { + curl_setopt($ch, CURLOPT_NOBODY, 1); + } + if ($conn_type == "COPY") { + curl_setopt($ch, CURLOPT_NOBODY, 1); + } + $this->connections[$conn_type] = $ch; + return; + } + + private function _reset_callback_vars() + { + $this->_text_list = array(); + $this->_return_list = NULL; + $this->_account_container_count = 0; + $this->_account_bytes_used = 0; + $this->_container_object_count = 0; + $this->_container_bytes_used = 0; + $this->_obj_etag = NULL; + $this->_obj_last_modified = NULL; + $this->_obj_content_type = NULL; + $this->_obj_content_length = NULL; + $this->_obj_metadata = array(); + $this->_obj_manifest = NULL; + $this->_obj_headers = NULL; + $this->_obj_write_string = ""; + $this->_cdn_streaming_uri = NULL; + $this->_cdn_enabled = NULL; + $this->_cdn_ssl_uri = NULL; + $this->_cdn_uri = NULL; + $this->_cdn_ttl = NULL; + $this->response_status = 0; + $this->response_reason = ""; + } + + private function _make_path($t="STORAGE",$c=NULL,$o=NULL) + { + $path = array(); + switch ($t) { + case "STORAGE": + $path[] = $this->storage_url; break; + case "CDN": + $path[] = $this->cdnm_url; break; + } + if ($c == "0") + $path[] = rawurlencode($c); + + if ($c) { + $path[] = rawurlencode($c); + } + if ($o) { + # mimic Python''s urllib.quote() feature of a "safe" '/' character + # + $path[] = str_replace("%2F","/",rawurlencode($o)); + } + return implode("/",$path); + } + + private function _headers(&$obj) + { + $hdrs = self::_process_headers($obj->metadata, $obj->headers); + if ($obj->manifest) + $hdrs[MANIFEST_HEADER] = $obj->manifest; + + return $hdrs; + } + + private function _process_headers($metadata=null, $headers=null) + { + $rules = array( + array( + 'prefix' => METADATA_HEADER_PREFIX, + ), + array( + 'prefix' => '', + 'filter' => array( # key order is important, first match decides + CONTENT_TYPE_HEADER => false, + CONTENT_LENGTH_HEADER => false, + CONTENT_HEADER_PREFIX => true, + ACCESS_CONTROL_HEADER_PREFIX => true, + ORIGIN_HEADER => true, + ), + ), + ); + + $hdrs = array(); + $argc = func_num_args(); + $argv = func_get_args(); + for ($argi = 0; $argi < $argc; $argi++) { + if(!is_array($argv[$argi])) continue; + + $rule = $rules[$argi]; + foreach ($argv[$argi] as $k => $v) { + $k = trim($k); + $v = trim($v); + if (strpos($k, ":") !== False) throw new SyntaxException( + "Header names cannot contain a ':' character."); + + if (array_key_exists('filter', $rule)) { + $result = null; + foreach ($rule['filter'] as $p => $f) { + if (strncasecmp($k, $p, strlen($p)) == 0) { + $result = $f; + break; + } + } + if (!$result) throw new SyntaxException(sprintf( + "Header name %s is not allowed", $k)); + } + + $k = $rule['prefix'] . $k; + if (strlen($k) > MAX_HEADER_NAME_LEN || strlen($v) > MAX_HEADER_VALUE_LEN) + throw new SyntaxException(sprintf( + "Header %s exceeds maximum length: %d/%d", + $k, strlen($k), strlen($v))); + + $hdrs[$k] = $v; + } + } + + return $hdrs; + } + + private function _send_request($conn_type, $url_path, $hdrs=NULL, $method="GET", $force_new=False) + { + $this->_init($conn_type, $force_new); + $this->_reset_callback_vars(); + $headers = $this->_make_headers($hdrs); + + if (gettype($this->connections[$conn_type]) == "unknown type") + throw new ConnectionNotOpenException ( + "Connection is not open." + ); + + switch ($method) { + case "COPY": + curl_setopt($this->connections[$conn_type], + CURLOPT_CUSTOMREQUEST, "COPY"); + break; + case "DELETE": + curl_setopt($this->connections[$conn_type], + CURLOPT_CUSTOMREQUEST, "DELETE"); + break; + case "POST": + curl_setopt($this->connections[$conn_type], + CURLOPT_CUSTOMREQUEST, "POST"); + default: + break; + } + + curl_setopt($this->connections[$conn_type], + CURLOPT_HTTPHEADER, $headers); + + curl_setopt($this->connections[$conn_type], + CURLOPT_URL, $url_path); + + if (!curl_exec($this->connections[$conn_type]) && curl_errno($this->connections[$conn_type]) !== 0) { + $this->error_str = "(curl error: " + . curl_errno($this->connections[$conn_type]) . ") "; + $this->error_str .= curl_error($this->connections[$conn_type]); + return False; + } + return curl_getinfo($this->connections[$conn_type], CURLINFO_HTTP_CODE); + } + + function close() + { + foreach ($this->connections as $cnx) { + if (isset($cnx)) { + curl_close($cnx); + $this->connections[$cnx] = NULL; + } + } + } + private function create_array() + { + $this->_text_list = explode("\n",rtrim($this->_return_list,"\n\x0B")); + return True; + } + +} + +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * c-hanging-comment-ender-p: nil + * End: + */ +?> diff --git a/apps/calendar/ajax/changeview.php b/apps/calendar/ajax/changeview.php index ae48b229b1..76e29cbe26 100644 --- a/apps/calendar/ajax/changeview.php +++ b/apps/calendar/ajax/changeview.php @@ -1,12 +1,12 @@ + * Copyright (c) 2012 Georg Ehrke * This file is licensed under the Affero General Public License version 3 or * later. * See the COPYING-README file. */ -require_once ("../../../lib/base.php"); +require_once ('../../../lib/base.php'); OC_JSON::checkLoggedIn(); OC_JSON::checkAppEnabled('calendar'); $view = $_GET['v']; diff --git a/apps/calendar/ajax/event/delete.php b/apps/calendar/ajax/event/delete.php index 5fc12900ef..fd7b709393 100644 --- a/apps/calendar/ajax/event/delete.php +++ b/apps/calendar/ajax/event/delete.php @@ -11,7 +11,10 @@ OC_JSON::checkLoggedIn(); OC_JSON::checkAppEnabled('calendar'); $id = $_POST['id']; -$event_object = OC_Calendar_App::getEventObject($id); +$access = OC_Calendar_App::getaccess($id, OC_Calendar_App::EVENT); +if($access != 'owner' && $access != 'rw'){ + OC_JSON::error(array('message'=>'permission denied')); + exit; +} $result = OC_Calendar_Object::delete($id); OC_JSON::success(); -?> diff --git a/apps/calendar/ajax/event/edit.form.php b/apps/calendar/ajax/event/edit.form.php index 7cbf3a87cf..97b0907c83 100644 --- a/apps/calendar/ajax/event/edit.form.php +++ b/apps/calendar/ajax/event/edit.form.php @@ -14,7 +14,13 @@ if(!OC_USER::isLoggedIn()) { OC_JSON::checkAppEnabled('calendar'); $id = $_GET['id']; -$data = OC_Calendar_App::getEventObject($id); +$data = OC_Calendar_App::getEventObject($id, true, true); + +if(!$data){ + OC_JSON::error(array('data' => array('message' => self::$l10n->t('Wrong calendar')))); + exit; +} +$access = OC_Calendar_App::getaccess($id, OC_Calendar_Share::EVENT); $object = OC_VObject::parse($data['calendardata']); $vevent = $object->VEVENT; @@ -182,8 +188,12 @@ if($data['repeating'] == 1){ }else{ $repeat['repeat'] = 'doesnotrepeat'; } - -$calendar_options = OC_Calendar_Calendar::allCalendars(OC_User::getUser()); +if($access == 'owner'){ + $calendar_options = OC_Calendar_Calendar::allCalendars(OC_User::getUser()); +}else{ + $calendar_options = array(OC_Calendar_App::getCalendar($data['calendarid'], false)); +} +$category_options = OC_Calendar_App::getCategoryOptions(); $repeat_options = OC_Calendar_App::getRepeatOptions(); $repeat_end_options = OC_Calendar_App::getEndOptions(); $repeat_month_options = OC_Calendar_App::getMonthOptions(); @@ -195,8 +205,14 @@ $repeat_bymonth_options = OC_Calendar_App::getByMonthOptions(); $repeat_byweekno_options = OC_Calendar_App::getByWeekNoOptions(); $repeat_bymonthday_options = OC_Calendar_App::getByMonthDayOptions(); -$tmpl = new OC_Template('calendar', 'part.editevent'); -$tmpl->assign('id', $id); +if($access == 'owner' || $access == 'rw'){ + $tmpl = new OC_Template('calendar', 'part.editevent'); +}elseif($access == 'r'){ + $tmpl = new OC_Template('calendar', 'part.showevent'); +} + +$tmpl->assign('eventid', $id); +$tmpl->assign('access', $access); $tmpl->assign('lastmodified', $lastmodified); $tmpl->assign('calendar_options', $calendar_options); $tmpl->assign('repeat_options', $repeat_options); diff --git a/apps/calendar/ajax/event/edit.php b/apps/calendar/ajax/event/edit.php index 64daffddef..f65b67b84a 100644 --- a/apps/calendar/ajax/event/edit.php +++ b/apps/calendar/ajax/event/edit.php @@ -10,21 +10,34 @@ require_once('../../../../lib/base.php'); OC_JSON::checkLoggedIn(); OC_JSON::checkAppEnabled('calendar'); +$id = $_POST['id']; + +if(!array_key_exists('calendar', $_POST)){ + $cal = OC_Calendar_Object::getCalendarid($id); + $_POST['calendar'] = $cal; +}else{ + $cal = $_POST['calendar']; +} + +$access = OC_Calendar_App::getaccess($id, OC_Calendar_App::EVENT); +if($access != 'owner' && $access != 'rw'){ + OC_JSON::error(array('message'=>'permission denied')); + exit; +} + $errarr = OC_Calendar_Object::validateRequest($_POST); if($errarr){ //show validate errors OC_JSON::error($errarr); exit; }else{ - $id = $_POST['id']; - $cal = $_POST['calendar']; - $data = OC_Calendar_App::getEventObject($id); + $data = OC_Calendar_App::getEventObject($id, false, false); $vcalendar = OC_VObject::parse($data['calendardata']); OC_Calendar_App::isNotModified($vcalendar->VEVENT, $_POST['lastmodified']); OC_Calendar_Object::updateVCalendarFromRequest($_POST, $vcalendar); - $result = OC_Calendar_Object::edit($id, $vcalendar->serialize()); + OC_Calendar_Object::edit($id, $vcalendar->serialize()); if ($data['calendarid'] != $cal) { OC_Calendar_Object::moveToCalendar($id, $cal); } diff --git a/apps/calendar/ajax/event/move.php b/apps/calendar/ajax/event/move.php index 37420e2a87..63995602de 100644 --- a/apps/calendar/ajax/event/move.php +++ b/apps/calendar/ajax/event/move.php @@ -9,15 +9,18 @@ require_once('../../../../lib/base.php'); OC_JSON::checkLoggedIn(); $id = $_POST['id']; - -$vcalendar = OC_Calendar_App::getVCalendar($id); +$access = OC_Calendar_App::getaccess($id, OC_Calendar_App::EVENT); +if($access != 'owner' && $access != 'rw'){ + OC_JSON::error(array('message'=>'permission denied')); + exit; +} +$vcalendar = OC_Calendar_App::getVCalendar($id, false, false); $vevent = $vcalendar->VEVENT; $allday = $_POST['allDay']; $delta = new DateInterval('P0D'); $delta->d = $_POST['dayDelta']; $delta->i = $_POST['minuteDelta']; - OC_Calendar_App::isNotModified($vevent, $_POST['lastmodified']); $dtstart = $vevent->DTSTART; diff --git a/apps/calendar/ajax/event/new.form.php b/apps/calendar/ajax/event/new.form.php index 838002a3a0..91b9554656 100644 --- a/apps/calendar/ajax/event/new.form.php +++ b/apps/calendar/ajax/event/new.form.php @@ -44,6 +44,7 @@ $repeat_byweekno_options = OC_Calendar_App::getByWeekNoOptions(); $repeat_bymonthday_options = OC_Calendar_App::getByMonthDayOptions(); $tmpl = new OC_Template('calendar', 'part.newevent'); +$tmpl->assign('access', 'owner'); $tmpl->assign('calendar_options', $calendar_options); $tmpl->assign('repeat_options', $repeat_options); $tmpl->assign('repeat_month_options', $repeat_month_options); diff --git a/apps/calendar/ajax/event/resize.php b/apps/calendar/ajax/event/resize.php index a8d8f49ae0..75d6e0b254 100644 --- a/apps/calendar/ajax/event/resize.php +++ b/apps/calendar/ajax/event/resize.php @@ -10,7 +10,13 @@ OC_JSON::checkLoggedIn(); $id = $_POST['id']; -$vcalendar = OC_Calendar_App::getVCalendar($id); +$access = OC_Calendar_App::getaccess($id, OC_Calendar_App::EVENT); +if($access != 'owner' && $access != 'rw'){ + OC_JSON::error(array('message'=>'permission denied')); + exit; +} + +$vcalendar = OC_Calendar_App::getVCalendar($id, false, false); $vevent = $vcalendar->VEVENT; $delta = new DateInterval('P0D'); @@ -27,6 +33,6 @@ unset($vevent->DURATION); $vevent->setDateTime('LAST-MODIFIED', 'now', Sabre_VObject_Property_DateTime::UTC); $vevent->setDateTime('DTSTAMP', 'now', Sabre_VObject_Property_DateTime::UTC); -$result = OC_Calendar_Object::edit($id, $vcalendar->serialize()); +OC_Calendar_Object::edit($id, $vcalendar->serialize()); $lastmodified = $vevent->__get('LAST-MODIFIED')->getDateTime(); OC_JSON::success(array('lastmodified'=>(int)$lastmodified->format('U'))); diff --git a/apps/calendar/ajax/events.php b/apps/calendar/ajax/events.php index 76dd1b4974..5b3d26b53b 100755 --- a/apps/calendar/ajax/events.php +++ b/apps/calendar/ajax/events.php @@ -1,6 +1,6 @@ + * Copyright (c) 2011, 2012 Georg Ehrke * This file is licensed under the Affero General Public License version 3 or * later. * See the COPYING-README file. @@ -8,102 +8,18 @@ require_once ('../../../lib/base.php'); require_once('when/When.php'); -$l = OC_L10N::get('calendar'); -$unnamed = $l->t('unnamed'); -function create_return_event($event, $vevent){ - $return_event = array(); - global $unnamed; - $return_event['id'] = (int)$event['id']; - $return_event['title'] = htmlspecialchars(($event['summary']!=NULL || $event['summary'] != '')?$event['summary']: $unnamed); - $return_event['description'] = isset($vevent->DESCRIPTION)?htmlspecialchars($vevent->DESCRIPTION->value):''; - $last_modified = $vevent->__get('LAST-MODIFIED'); - if ($last_modified){ - $lastmodified = $last_modified->getDateTime()->format('U'); - }else{ - $lastmodified = 0; - } - $return_event['lastmodified'] = (int)$lastmodified; - return $return_event; -} OC_JSON::checkLoggedIn(); OC_JSON::checkAppEnabled('calendar'); -if(version_compare(PHP_VERSION, '5.3.0', '>=')){ - $start = DateTime::createFromFormat('U', $_GET['start']); - $end = DateTime::createFromFormat('U', $_GET['end']); -}else{ - $start = new DateTime('@' . $_GET['start']); - $end = new DateTime('@' . $_GET['end']); -} +$start = (version_compare(PHP_VERSION, '5.3.0', '>='))?DateTime::createFromFormat('U', $_GET['start']):new DateTime('@' . $_GET['start']); +$end = (version_compare(PHP_VERSION, '5.3.0', '>='))?DateTime::createFromFormat('U', $_GET['end']):new DateTime('@' . $_GET['end']); -$calendar_id = $_GET['calendar_id']; -if (is_numeric($calendar_id)) { - $calendar = OC_Calendar_App::getCalendar($calendar_id); - OC_Response::enableCaching(0); - OC_Response::setETagHeader($calendar['ctag']); - $events = OC_Calendar_Object::allInPeriod($calendar_id, $start, $end); -} else { - $events = array(); - OC_Hook::emit('OC_Calendar', 'getEvents', array('calendar_id' => $calendar_id, 'events' => &$events)); -} +$events = OC_Calendar_App::getrequestedEvents($_GET['calendar_id'], $start, $end); -$user_timezone = OC_Preferences::getValue(OC_USER::getUser(), 'calendar', 'timezone', date_default_timezone_get()); -$return = array(); +$output = array(); foreach($events as $event){ - if (isset($event['calendardata'])) { - $object = OC_VObject::parse($event['calendardata']); - $vevent = $object->VEVENT; - } else { - $vevent = $event['vevent']; - } - - $return_event = create_return_event($event, $vevent); - - $dtstart = $vevent->DTSTART; - $start_dt = $dtstart->getDateTime(); - $dtend = OC_Calendar_Object::getDTEndFromVEvent($vevent); - $end_dt = $dtend->getDateTime(); - if ($dtstart->getDateType() == Sabre_VObject_Property_DateTime::DATE){ - $return_event['allDay'] = true; - }else{ - $return_event['allDay'] = false; - $start_dt->setTimezone(new DateTimeZone($user_timezone)); - $end_dt->setTimezone(new DateTimeZone($user_timezone)); - } - - //Repeating Events - if($event['repeating'] == 1){ - $duration = (double) $end_dt->format('U') - (double) $start_dt->format('U'); - $r = new When(); - $r->recur($start_dt)->rrule((string) $vevent->RRULE); - while($result = $r->next()){ - if($result < $start){ - continue; - } - if($result > $end){ - break; - } - if($return_event['allDay'] == true){ - $return_event['start'] = $result->format('Y-m-d'); - $return_event['end'] = date('Y-m-d', $result->format('U') + --$duration); - }else{ - $return_event['start'] = $result->format('Y-m-d H:i:s'); - $return_event['end'] = date('Y-m-d H:i:s', $result->format('U') + $duration); - } - $return[] = $return_event; - } - }else{ - if($return_event['allDay'] == true){ - $return_event['start'] = $start_dt->format('Y-m-d'); - $end_dt->modify('-1 sec'); - $return_event['end'] = $end_dt->format('Y-m-d'); - }else{ - $return_event['start'] = $start_dt->format('Y-m-d H:i:s'); - $return_event['end'] = $end_dt->format('Y-m-d H:i:s'); - } - $return[] = $return_event; - } + $output[] = OC_Calendar_App::generateEventOutput($event, $start, $end); } -OC_JSON::encodedPrint($return); +OC_JSON::encodedPrint($output); ?> diff --git a/apps/calendar/ajax/import/import.php b/apps/calendar/ajax/import/import.php index d0bdab4f0d..4d5e7340ae 100644 --- a/apps/calendar/ajax/import/import.php +++ b/apps/calendar/ajax/import/import.php @@ -7,11 +7,12 @@ */ //check for calendar rights or create new one ob_start(); -require_once('../../../../lib/base.php'); +require_once ('../../../../lib/base.php'); OC_JSON::checkLoggedIn(); OC_Util::checkAppEnabled('calendar'); -$nl = "\n\r"; -$progressfile = OC::$APPSROOT . '/apps/calendar/import_tmp/' . md5(session_id()) . '.txt'; +$nl="\r\n"; +$comps = array('VEVENT'=>true, 'VTODO'=>true, 'VJOURNAL'=>true); +$progressfile = 'import_tmp/' . md5(session_id()) . '.txt'; if(is_writable('import_tmp/')){ $progressfopen = fopen($progressfile, 'w'); fwrite($progressfopen, '10'); @@ -29,85 +30,94 @@ if($_POST['method'] == 'new'){ } $id = $_POST['id']; } -//analyse the calendar file if(is_writable('import_tmp/')){ $progressfopen = fopen($progressfile, 'w'); fwrite($progressfopen, '20'); fclose($progressfopen); } -$searchfor = array('VEVENT', 'VTODO', 'VJOURNAL'); -$parts = $searchfor; -$filearr = explode($nl, $file); -$inelement = false; -$parts = array(); +// normalize the newlines +$file = str_replace(array("\r","\n\n"), array("\n","\n"), $file); +$lines = explode("\n", $file); +unset($file); +if(is_writable('import_tmp/')){ + $progressfopen = fopen($progressfile, 'w'); + fwrite($progressfopen, '30'); + fclose($progressfopen); +} +// analyze the file, group components by uid, and keep refs to originating calendar object +// $cals is array calendar objects, keys are 1st line# $cal, ie array( $cal => $caldata ) +// $caldata is array( 'first' => 1st component line#, 'last' => last comp line#, 'end' => end line# ) +// $caldata is used to create prefix/suffix strings when building import text +// $uids is array of component arrays, keys are $uid, ie array( $uid => array( $beginlineno => $component ) ) +// $component is array( 'end' => end line#, 'cal'=> $cal ) +$comp=$uid=$cal=false; +$cals=$uids=array(); $i = 0; -foreach($filearr as $line){ - foreach($searchfor as $search){ - if(substr_count($line, $search) == 1){ - list($attr, $val) = explode(':', $line); - if($attr == 'BEGIN'){ - $parts[]['begin'] = $i; - $inelement = true; +foreach($lines as $line) { + + if(strpos($line, ':')!==false) { + list($attr, $val) = explode(':', strtoupper($line)); + if ($attr == 'BEGIN' && $val == 'VCALENDAR') { + $cal = $i; + $cals[$cal] = array('first'=>$i,'last'=>$i,'end'=>$i); + } elseif ($attr =='BEGIN' && $cal!==false && isset($comps[$val])) { + $comp = $val; + $beginNo = $i; + } elseif ($attr == 'END' && $cal!==false && $val == 'VCALENDAR') { + if($comp!==false) { + unset($cals[$cal]); // corrupt calendar, unset it + } else { + $cals[$cal]['end'] = $i; } - if($attr == 'END'){ - $parts[count($parts) - 1]['end'] = $i; - $inelement = false; + $comp=$uid=$cal=false; // reset calendar + } elseif ($attr == 'END' && $comp!==false && $val == $comp) { + if(! $uid) { + $uid = OC_Calendar_Object::createUID(); } + $uids[$uid][$beginNo] = array('end'=>$i, 'cal'=>$cal); + if ($cals[$cal]['first'] == $cal) { + $cals[$cal]['first'] = $beginNo; + } + $cals[$cal]['last'] = $i; + $comp=$uid=false; // reset component + } elseif ($attr =="UID" && $comp!==false) { + list($attr, $uid) = explode(':', $line); } } $i++; } -//import the calendar +// import the calendar if(is_writable('import_tmp/')){ $progressfopen = fopen($progressfile, 'w'); - fwrite($progressfopen, '40'); + fwrite($progressfopen, '60'); fclose($progressfopen); } -$start = ''; -for ($i = 0; $i < $parts[0]['begin']; $i++) { - if($i == 0){ - $start = $filearr[0]; - }else{ - $start .= $nl . $filearr[$i]; - } -} -$end = ''; -for($i = $parts[count($parts) - 1]['end'] + 1;$i <= count($filearr) - 1; $i++){ - if($i == $parts[count($parts) - 1]['end'] + 1){ - $end = $filearr[$parts[count($parts) - 1]['end'] + 1]; - }else{ - $end .= $nl . $filearr[$i]; - } -} -if(is_writable('import_tmp/')){ - $progressfopen = fopen($progressfile, 'w'); - fwrite($progressfopen, '50'); - fclose($progressfopen); -} -$importready = array(); -foreach($parts as $part){ - for($i = $part['begin']; $i <= $part['end'];$i++){ - if($i == $part['begin']){ - $content = $filearr[$i]; - }else{ - $content .= $nl . $filearr[$i]; +foreach($uids as $uid) { + + $prefix=$suffix=$content=array(); + foreach($uid as $begin=>$details) { + + $cal = $details['cal']; + if(!isset($cals[$cal])) { + continue; // from corrupt/incomplete calendar } + $cdata = $cals[$cal]; + // if we have multiple components from different calendar objects, + // we should really merge their elements (enhancement?) -- 1st one wins for now. + if(! count($prefix)) { + $prefix = array_slice($lines, $cal, $cdata['first'] - $cal); + } + if(! count($suffix)) { + $suffix = array_slice($lines, $cdata['last']+1, $cdata['end'] - $cdata['last']); + } + $content = array_merge($content, array_slice($lines, $begin, $details['end'] - $begin + 1)); } - $importready[] = $start . $nl . $content . $nl . $end; -} -if(is_writable('import_tmp/')){ - $progressfopen = fopen($progressfile, 'w'); - fwrite($progressfopen, '70'); - fclose($progressfopen); -} -if(count($parts) == 1){ - OC_Calendar_Object::add($id, $file); -}else{ - foreach($importready as $import){ + if(count($content)) { + $import = join($nl, array_merge($prefix, $content, $suffix)) . $nl; OC_Calendar_Object::add($id, $import); } } -//done the import +// finished import if(is_writable('import_tmp/')){ $progressfopen = fopen($progressfile, 'w'); fwrite($progressfopen, '100'); @@ -117,4 +127,4 @@ sleep(3); if(is_writable('import_tmp/')){ unlink($progressfile); } -OC_JSON::success(); +OC_JSON::success(); \ No newline at end of file diff --git a/apps/calendar/ajax/share/activation.php b/apps/calendar/ajax/share/activation.php new file mode 100644 index 0000000000..a4a3ce4819 --- /dev/null +++ b/apps/calendar/ajax/share/activation.php @@ -0,0 +1,12 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ +require_once('../../../../lib/base.php'); +$id = strip_tags($_GET['id']); +$activation = strip_tags($_GET['activation']); +OC_Calendar_Share::set_active(OC_User::getUser(), $id, $activation); +OC_JSON::success(); \ No newline at end of file diff --git a/apps/calendar/ajax/share/changepermission.php b/apps/calendar/ajax/share/changepermission.php index d91f87b613..41d49e7b52 100644 --- a/apps/calendar/ajax/share/changepermission.php +++ b/apps/calendar/ajax/share/changepermission.php @@ -36,5 +36,5 @@ if($sharetype == 'group' && !OC_Group::groupExists($sharewith)){ OC_JSON::error(array('message'=>'group not found')); exit; } -$success = OC_Calendar_Share::changepermission($sharewith, $sharetype, $id, $permission, (($idtype=='calendar') ? OC_Calendar_Share::CALENDAR : OC_Calendar_Share::Event)); +$success = OC_Calendar_Share::changepermission($sharewith, $sharetype, $id, $permission, (($idtype=='calendar') ? OC_Calendar_Share::CALENDAR : OC_Calendar_Share::EVENT)); OC_JSON::success(); \ No newline at end of file diff --git a/apps/calendar/ajax/share/share.php b/apps/calendar/ajax/share/share.php index d892727701..2e81040b47 100644 --- a/apps/calendar/ajax/share/share.php +++ b/apps/calendar/ajax/share/share.php @@ -16,6 +16,14 @@ switch($idtype){ OC_JSON::error(array('message'=>'unexspected parameter')); exit; } +if($idtype == 'calendar' && !OC_Calendar_App::getCalendar($id)){ + OC_JSON::error(array('message'=>'permission denied')); + exit; +} +if($idtype == 'event' && !OC_Calendar_App::getEventObject($id)){ + OC_JSON::error(array('message'=>'permission denied')); + exit; +} $sharewith = $_GET['sharewith']; $sharetype = strip_tags($_GET['sharetype']); switch($sharetype){ @@ -38,7 +46,7 @@ if($sharetype == 'group' && !OC_Group::groupExists($sharewith)){ if($sharetype == 'user' && OC_User::getUser() == $sharewith){ OC_JSON::error(array('meesage'=>'you can not share with yourself')); } -$success = OC_Calendar_Share::share(OC_User::getUser(), $sharewith, $sharetype, $id, (($idtype=='calendar') ? OC_Calendar_Share::CALENDAR : OC_Calendar_Share::Event)); +$success = OC_Calendar_Share::share(OC_User::getUser(), $sharewith, $sharetype, $id, (($idtype=='calendar') ? OC_Calendar_Share::CALENDAR : OC_Calendar_Share::EVENT)); if($success){ if($sharetype == 'public'){ OC_JSON::success(array('message'=>$success)); diff --git a/apps/calendar/ajax/share/unshare.php b/apps/calendar/ajax/share/unshare.php index ec3150a89a..5bedbaaf0a 100644 --- a/apps/calendar/ajax/share/unshare.php +++ b/apps/calendar/ajax/share/unshare.php @@ -30,12 +30,11 @@ switch($sharetype){ if($sharetype == 'user' && !OC_User::userExists($sharewith)){ OC_JSON::error(array('message'=>'user not found')); exit; -} -if($sharetype == 'group' && !OC_Group::groupExists($sharewith)){ +}elseif($sharetype == 'group' && !OC_Group::groupExists($sharewith)){ OC_JSON::error(array('message'=>'group not found')); exit; } -$success = OC_Calendar_Share::unshare(OC_User::getUser(), $sharewith, $sharetype, $id, (($idtype=='calendar') ? OC_Calendar_Share::CALENDAR : OC_Calendar_Share::Event)); +$success = OC_Calendar_Share::unshare(OC_User::getUser(), $sharewith, $sharetype, $id, (($idtype=='calendar') ? OC_Calendar_Share::CALENDAR : OC_Calendar_Share::EVENT)); if($success){ OC_JSON::success(); }else{ diff --git a/apps/calendar/appinfo/app.php b/apps/calendar/appinfo/app.php index ea743674fe..21d128e7b8 100644 --- a/apps/calendar/appinfo/app.php +++ b/apps/calendar/appinfo/app.php @@ -5,9 +5,12 @@ OC::$CLASSPATH['OC_Calendar_Calendar'] = 'apps/calendar/lib/calendar.php'; OC::$CLASSPATH['OC_Calendar_Object'] = 'apps/calendar/lib/object.php'; OC::$CLASSPATH['OC_Calendar_Hooks'] = 'apps/calendar/lib/hooks.php'; OC::$CLASSPATH['OC_Connector_Sabre_CalDAV'] = 'apps/calendar/lib/connector_sabre.php'; +OC::$CLASSPATH['OC_Calendar_Share'] = 'apps/calendar/lib/share.php'; OC::$CLASSPATH['OC_Search_Provider_Calendar'] = 'apps/calendar/lib/search.php'; OC_HOOK::connect('OC_User', 'post_deleteUser', 'OC_Calendar_Hooks', 'deleteUser'); OC_Util::addScript('calendar','loader'); +OC_Util::addScript("3rdparty", "chosen/chosen.jquery.min"); +OC_Util::addStyle("3rdparty", "chosen/chosen"); OC_App::register( array( 'order' => 10, 'id' => 'calendar', @@ -19,4 +22,4 @@ OC_App::addNavigationEntry( array( 'icon' => OC_Helper::imagePath( 'calendar', 'icon.svg' ), 'name' => $l->t('Calendar'))); OC_App::registerPersonal('calendar', 'settings'); -OC_Search::registerProvider('OC_Search_Provider_Calendar'); +OC_Search::registerProvider('OC_Search_Provider_Calendar'); \ No newline at end of file diff --git a/apps/calendar/appinfo/database.xml b/apps/calendar/appinfo/database.xml index 7f7b645755..b065ab3f94 100644 --- a/apps/calendar/appinfo/database.xml +++ b/apps/calendar/appinfo/database.xml @@ -187,5 +187,107 @@ + + + *dbprefix*calendar_share_event + + + + + owner + text + true + 255 + + + + share + text + true + 255 + + + + sharetype + text + true + 6 + + + + eventid + integer + + true + true + 11 + + + + permissions + integer + true + 1 + + + + +
+ + + + *dbprefix*calendar_share_calendar + + + + + owner + text + true + 255 + + + + share + text + true + 255 + + + + sharetype + text + true + 6 + + + + calendarid + integer + + true + true + 11 + + + + permissions + integer + true + 1 + + + + active + integer + 1 + true + 4 + + + + +
+ diff --git a/apps/calendar/appinfo/version b/apps/calendar/appinfo/version index 7dff5b8921..1d71ef9744 100644 --- a/apps/calendar/appinfo/version +++ b/apps/calendar/appinfo/version @@ -1 +1 @@ -0.2.1 \ No newline at end of file +0.3 \ No newline at end of file diff --git a/apps/calendar/css/style.css b/apps/calendar/css/style.css index cffaf35640..373a456563 100644 --- a/apps/calendar/css/style.css +++ b/apps/calendar/css/style.css @@ -56,6 +56,12 @@ button.category{margin:0 3px;} .calendar-colorpicker-color{display:inline-block;width:20px;height:20px;margin-right:2px;cursor:pointer;border:2px solid transparent;} .calendar-colorpicker-color.active{border:2px solid black;} +#event {padding: 0;margin: 0;margin-top:-5px} + +.calendar_share_dropdown{ display:block; position:absolute; z-index:100; width:16em; right:0; margin-right:7em; background:#F8F8F8; padding:1em; +-moz-box-shadow:0 1px 1px #777; -webkit-box-shadow:0 1px 1px #777; box-shadow:0 1px 1px #777; +-moz-border-radius:0 0 1em 1em; -webkit-border-radius:0 0 1em 1em; border-radius:0 0 1em 1em;} + .fc-list-table { margin: 10px; diff --git a/apps/calendar/export.php b/apps/calendar/export.php index 2736eec96c..95cba03906 100644 --- a/apps/calendar/export.php +++ b/apps/calendar/export.php @@ -11,9 +11,9 @@ OC_Util::checkLoggedIn(); OC_Util::checkAppEnabled('calendar'); $cal = isset($_GET['calid']) ? $_GET['calid'] : NULL; $event = isset($_GET['eventid']) ? $_GET['eventid'] : NULL; -$nl = "\n\r"; +$nl = "\r\n"; if(isset($cal)){ - $calendar = OC_Calendar_App::getCalendar($cal); + $calendar = OC_Calendar_App::getCalendar($cal, true); $calobjects = OC_Calendar_Object::all($cal); header('Content-Type: text/Calendar'); header('Content-Disposition: inline; filename=' . $calendar['displayname'] . '.ics'); @@ -21,7 +21,7 @@ if(isset($cal)){ echo $calobject['calendardata'] . $nl; } }elseif(isset($event)){ - $data = OC_Calendar_App::getEventObject($_GET['eventid']); + $data = OC_Calendar_App::getEventObject($_GET['eventid'], true); $calendarid = $data['calendarid']; $calendar = OC_Calendar_App::getCalendar($calendarid); header('Content-Type: text/Calendar'); diff --git a/apps/calendar/import.php b/apps/calendar/import.php deleted file mode 100644 index b1c6f91df8..0000000000 --- a/apps/calendar/import.php +++ /dev/null @@ -1,120 +0,0 @@ - - * This file is licensed under the Affero General Public License version 3 or - * later. - * See the COPYING-README file. - */ -//check for calendar rights or create new one -ob_start(); -require_once ('../../lib/base.php'); -OC_JSON::checkLoggedIn(); -OC_Util::checkAppEnabled('calendar'); -$nl = "\n"; -$progressfile = 'import_tmp/' . md5(session_id()) . '.txt'; -if(is_writable('import_tmp/')){ - $progressfopen = fopen($progressfile, 'w'); - fwrite($progressfopen, '10'); - fclose($progressfopen); -} -$file = OC_Filesystem::file_get_contents($_POST['path'] . '/' . $_POST['file']); -if($_POST['method'] == 'new'){ - $id = OC_Calendar_Calendar::addCalendar(OC_User::getUser(), $_POST['calname']); - OC_Calendar_Calendar::setCalendarActive($id, 1); -}else{ - $calendar = OC_Calendar_App::getCalendar($_POST['id']); - if($calendar['userid'] != OC_USER::getUser()){ - OC_JSON::error(); - exit(); - } - $id = $_POST['id']; -} -//analyse the calendar file -if(is_writable('import_tmp/')){ - $progressfopen = fopen($progressfile, 'w'); - fwrite($progressfopen, '20'); - fclose($progressfopen); -} -$searchfor = array('VEVENT', 'VTODO', 'VJOURNAL'); -$parts = $searchfor; -$filearr = explode($nl, $file); -$inelement = false; -$parts = array(); -$i = 0; -foreach($filearr as $line){ - foreach($searchfor as $search){ - if(substr_count($line, $search) == 1){ - list($attr, $val) = explode(':', $line); - if($attr == 'BEGIN'){ - $parts[]['begin'] = $i; - $inelement = true; - } - if($attr == 'END'){ - $parts[count($parts) - 1]['end'] = $i; - $inelement = false; - } - } - } - $i++; -} -//import the calendar -if(is_writable('import_tmp/')){ - $progressfopen = fopen($progressfile, 'w'); - fwrite($progressfopen, '40'); - fclose($progressfopen); -} -$start = ''; -for ($i = 0; $i < $parts[0]['begin']; $i++) { - if($i == 0){ - $start = $filearr[0]; - }else{ - $start .= $nl . $filearr[$i]; - } -} -$end = ''; -for($i = $parts[count($parts) - 1]['end'] + 1;$i <= count($filearr) - 1; $i++){ - if($i == $parts[count($parts) - 1]['end'] + 1){ - $end = $filearr[$parts[count($parts) - 1]['end'] + 1]; - }else{ - $end .= $nl . $filearr[$i]; - } -} -if(is_writable('import_tmp/')){ - $progressfopen = fopen($progressfile, 'w'); - fwrite($progressfopen, '50'); - fclose($progressfopen); -} -$importready = array(); -foreach($parts as $part){ - for($i = $part['begin']; $i <= $part['end'];$i++){ - if($i == $part['begin']){ - $content = $filearr[$i]; - }else{ - $content .= $nl . $filearr[$i]; - } - } - $importready[] = $start . $nl . $content . $nl . $end; -} -if(is_writable('import_tmp/')){ - $progressfopen = fopen($progressfile, 'w'); - fwrite($progressfopen, '70'); - fclose($progressfopen); -} -if(count($parts) == 1){ - OC_Calendar_Object::add($id, $file); -}else{ - foreach($importready as $import){ - OC_Calendar_Object::add($id, $import); - } -} -//done the import -if(is_writable('import_tmp/')){ - $progressfopen = fopen($progressfile, 'w'); - fwrite($progressfopen, '100'); - fclose($progressfopen); -} -sleep(3); -if(is_writable('import_tmp/')){ - unlink($progressfile); -} -OC_JSON::success(); \ No newline at end of file diff --git a/apps/calendar/index.php b/apps/calendar/index.php index f964a13ef7..c44de7f07d 100644 --- a/apps/calendar/index.php +++ b/apps/calendar/index.php @@ -21,6 +21,10 @@ $eventSources = array(); foreach($calendars as $calendar){ $eventSources[] = OC_Calendar_Calendar::getEventSourceInfo($calendar); } + +$eventSources[] = array('url' => 'ajax/events.php?calendar_id=shared_rw', 'backgroundColor' => '#1D2D44', 'borderColor' => '#888', 'textColor' => 'white', 'editable'=>'true'); +$eventSources[] = array('url' => 'ajax/events.php?calendar_id=shared_r', 'backgroundColor' => '#1D2D44', 'borderColor' => '#888', 'textColor' => 'white', 'editable' => 'false'); + OC_Hook::emit('OC_Calendar', 'getSources', array('sources' => &$eventSources)); $categories = OC_Calendar_App::getCategoryOptions(); diff --git a/apps/calendar/js/calendar.js b/apps/calendar/js/calendar.js index 858990fb89..a16856938c 100644 --- a/apps/calendar/js/calendar.js +++ b/apps/calendar/js/calendar.js @@ -46,6 +46,7 @@ Calendar={ $('#advanced_month').change(function(){ Calendar.UI.repeat('month'); }); + $( "#event" ).tabs({ selected: 0}); $('#event').dialog({ width : 500, close : function(event, ui) { @@ -455,7 +456,7 @@ Calendar={ $('#calendar_holder').fullCalendar('removeEventSource', data.eventSource.url); $('#calendar_holder').fullCalendar('addEventSource', data.eventSource); if (calendarid == 'new'){ - $('#choosecalendar_dialog > table').append(''); + $('#choosecalendar_dialog > table:first').append(''); } }else{ $("#displayname_"+calendarid).css('background-color', '#FF2626'); @@ -494,6 +495,109 @@ Calendar={ left: -10000 }); } + }, + Share:{ + currentid: 'false', + idtype: '', + activation:function(object,owner,id){ + $.getJSON(OC.filePath('calendar', 'ajax/share', 'activation.php'),{id:id, idtype:'calendar', activation:object.checked?1:0}); + $('#calendar_holder').fullCalendar('refetchEvents'); + }, + dropdown:function(userid, calid){ + $('.calendar_share_dropdown').remove(); + $('
').appendTo('#'+userid+'_'+calid); + $.get(OC.filePath('calendar', 'ajax/share', 'dropdown.php') + '?calid=' + calid, function(data){ + $('#'+userid+'_'+calid+' > .calendar_share_dropdown').html(data); + $('#'+userid+'_'+calid+' > .calendar_share_dropdown').show('blind'); + $('#share_user').chosen(); + $('#share_group').chosen(); + }); + Calendar.UI.Share.currentid = calid; + Calendar.UI.Share.idtype = 'calendar'; + }, + share:function(id, idtype, sharewith, sharetype){ + $.getJSON(OC.filePath('calendar', 'ajax/share', 'share.php'),{id:id, idtype:idtype, sharewith:sharewith, sharetype:sharetype}, function(data){ + + }); + }, + unshare:function(id, idtype, sharewith, sharetype){ + $.getJSON(OC.filePath('calendar', 'ajax/share', 'unshare.php'),{id:id, idtype:idtype, sharewith:sharewith, sharetype:sharetype}, function(){ + if(sharetype == 'public'){ + $('#public_token').val(''); + $('#public_token').css('display', 'none'); + } + }); + }, + changepermission:function(id, idtype, sharewith, sharetype, permission){ + $.getJSON(OC.filePath('calendar', 'ajax/share', 'changepermission.php'),{id:id, idtype:idtype, sharewith: sharewith, sharetype:sharetype, permission: (permission?1:0)}); + }, + init:function(){ + $('.calendar_share_dropdown').live('mouseleave', function(){ + $('.calendar_share_dropdown').hide('blind', function(){ + $('.calendar_share_dropdown').remove(); + }); + }); + $('#share_user').live('change', function(){ + if($('#sharewithuser_' + $('#share_user option:selected').text()).length == 0){ + Calendar.UI.Share.share(Calendar.UI.Share.currentid, Calendar.UI.Share.idtype, $('#share_user option:selected').text(), 'user'); + var newitem = '
  • ' + $('#share_user option:selected').text() + '
  • '; + $('#sharewithuser_list').append(newitem); + $('#sharewithuser_' + $('#share_user option:selected').text() + ' > img').click(function(){ + $('#share_user option[value="' + $(this).parent().text() + '"]').removeAttr('disabled'); + Calendar.UI.Share.unshare(Calendar.UI.Share.currentid, Calendar.UI.Share.idtype, $(this).parent().text(), 'user' ); + $("#share_user").trigger("liszt:updated"); + $(this).parent().remove(); + }); + $('#share_user option:selected').attr('disabled', 'disabled'); + $("#share_user").trigger("liszt:updated"); + } + }); + $('#share_group').live('change', function(){ + if($('#sharewithgroup_' + $('#share_group option:selected').text()).length == 0){ + Calendar.UI.Share.share(Calendar.UI.Share.currentid, Calendar.UI.Share.idtype, $('#share_group option:selected').text(), 'group'); + var newitem = '
  • ' + $('#share_group option:selected').text() + '
  • '; + $('#sharewithgroup_list').append(newitem); + $('#sharewithgroup_' + $('#share_group option:selected').text() + ' > img').click(function(){ + $('#share_group option[value="' + $(this).parent().text() + '"]').removeAttr('disabled'); + Calendar.UI.Share.unshare(Calendar.UI.Share.currentid, Calendar.UI.Share.idtype, $(this).parent().text(), 'group'); + $("#share_group").trigger("liszt:updated"); + $(this).parent().remove(); + }); + $('#share_group option:selected').attr('disabled', 'disabled'); + $("#share_group").trigger("liszt:updated"); + } + }); + $('#sharewithuser_list > li > input:checkbox').live('change', function(){ + Calendar.UI.Share.changepermission(Calendar.UI.Share.currentid, Calendar.UI.Share.idtype, $(this).parent().text(), 'user', this.checked); + }); + $('#sharewithgroup_list > li > input:checkbox').live('change', function(){ + Calendar.UI.Share.changepermission(Calendar.UI.Share.currentid, Calendar.UI.Share.idtype, $(this).parent().text(), 'group', this.checked); + }); + $('#publish').live('change', function(){ + if(this.checked == 1){ + Calendar.UI.Share.share(Calendar.UI.Share.currentid, Calendar.UI.Share.idtype, '', 'public'); + }else{ + Calendar.UI.Share.unshare(Calendar.UI.Share.currentid, Calendar.UI.Share.idtype, '', 'public'); + } + }); + $('#sharewithuser_list').live('mouseenter', function(){ + $('#sharewithuser_list > li > img').css('display', 'block'); + $('#sharewithuser_list > li > input').css('visibility', 'visible'); + }); + $('#sharewithuser_list').live('mouseleave', function(){ + $('#sharewithuser_list > li > img').css('display', 'none'); + $('#sharewithuser_list > li > input').css('visibility', 'hidden'); + }); + $('#sharewithgroup_list').live('mouseenter', function(){ + $('#sharewithgroup_list > li > img').css('display', 'block'); + $('#sharewithgroup_list > li > input').css('visibility', 'visible'); + }); + $('#sharewithgroup_list').live('mouseleave', function(){ + $('#sharewithgroup_list > li > img').css('display', 'none'); + $('#sharewithgroup_list > li > input').css('visibility', 'hidden'); + }); + /*var permissions = (this.checked) ? 1 : 0;*/ + } } } } @@ -749,4 +853,5 @@ $(document).ready(function(){ $('#datecontrol_right').click(function(){ $('#calendar_holder').fullCalendar('next'); }); + Calendar.UI.Share.init(); }); diff --git a/apps/calendar/js/loader.js b/apps/calendar/js/loader.js index 5400387975..60d92f448e 100644 --- a/apps/calendar/js/loader.js +++ b/apps/calendar/js/loader.js @@ -44,7 +44,7 @@ Calendar_Import={ $('#newcalendar').attr('readonly', 'readonly'); $('#calendar').attr('disabled', 'disabled'); var progressfile = $('#progressfile').val(); - $.post(OC.filePath('calendar', '', 'import.php'), {method: String (method), calname: String (calname), path: String (path), file: String (filename), id: String (calid)}, function(data){ + $.post(OC.filePath('calendar', 'ajax/import', 'import.php'), {method: String (method), calname: String (calname), path: String (path), file: String (filename), id: String (calid)}, function(data){ if(data.status == 'success'){ $('#progressbar').progressbar('option', 'value', 100); $('#import_done').css('display', 'block'); diff --git a/apps/calendar/lib/alarm.php b/apps/calendar/lib/alarm.php new file mode 100644 index 0000000000..a71cc08682 --- /dev/null +++ b/apps/calendar/lib/alarm.php @@ -0,0 +1,13 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ +/* + * This class manages reminders for calendars + */ +class OC_Calendar_Alarm{ + +} \ No newline at end of file diff --git a/apps/calendar/lib/app.php b/apps/calendar/lib/app.php index 4b481a4f28..3ce0d6fa1d 100644 --- a/apps/calendar/lib/app.php +++ b/apps/calendar/lib/app.php @@ -1,60 +1,121 @@ + * Copyright (c) 2012 Georg Ehrke * This file is licensed under the Affero General Public License version 3 or * later. * See the COPYING-README file. - */ - -/** + * * This class manages our app actions */ -OC_Calendar_App::$l10n = OC_L10N::get('calendar'); +OC_Calendar_App::$l10n = new OC_L10N('calendar'); +OC_Calendar_App::$tz = OC_Preferences::getValue(OC_USER::getUser(), 'calendar', 'timezone', date_default_timezone_get()); class OC_Calendar_App{ + const CALENDAR = 'calendar'; + const EVENT = 'event'; + /* + * @brief language object for calendar app + */ public static $l10n; + + /* + * @brief categories of the user + */ protected static $categories = null; - public static function getCalendar($id){ - $calendar = OC_Calendar_Calendar::find( $id ); - if( $calendar === false || $calendar['userid'] != OC_User::getUser()){ - OC_JSON::error(array('data' => array('message' => self::$l10n->t('Wrong calendar')))); - exit(); + /* + * @brief timezone of the user + */ + public static $tz; + + /* + * @brief returns informations about a calendar + * @param int $id - id of the calendar + * @param bool $security - check access rights or not + * @param bool $shared - check if the user got access via sharing + * @return mixed - bool / array + */ + public static function getCalendar($id, $security = true, $shared = false){ + $calendar = OC_Calendar_Calendar::find($id); + if($shared === true){ + if(OC_Calendar_Share::check_access(OC_User::getUser(), $id, OC_Calendar_Share::CALENDAR)){ + return $calendar; + } } - return $calendar; - } - - public static function getEventObject($id){ - $event_object = OC_Calendar_Object::find( $id ); - if( $event_object === false ){ - OC_JSON::error(); - exit(); + if($security === true){ + if($calendar['userid'] != OC_User::getUser()){ + return false; + } } - - self::getCalendar( $event_object['calendarid'] );//access check - return $event_object; - } - - public static function getVCalendar($id){ - $event_object = self::getEventObject( $id ); - - $vcalendar = OC_VObject::parse($event_object['calendardata']); - // Check if the vcalendar is valid - if(is_null($vcalendar)){ - OC_JSON::error(); - exit(); + if($calendar === false){ + return false; } - return $vcalendar; + return OC_Calendar_Calendar::find($id); } - - public static function isNotModified($vevent, $lastmodified) - { + + /* + * @brief returns informations about an event + * @param int $id - id of the event + * @param bool $security - check access rights or not + * @param bool $shared - check if the user got access via sharing + * @return mixed - bool / array + */ + public static function getEventObject($id, $security = true, $shared = false){ + $event = OC_Calendar_Object::find($id); + if($shared === true){ + if(OC_Calendar_Share::check_access(OC_User::getUser(), $id, OC_Calendar_Share::EVENT)){ + return $event; + } + } + if($security === true){ + $calendar = self::getCalendar($event['calendarid'], false); + if($calendar['userid'] != OC_User::getUser()){ + return false; + } + } + if($event === false){ + return false; + } + return $event; + } + + /* + * @brief returns the parsed calendar data + * @param int $id - id of the event + * @param bool $security - check access rights or not + * @return mixed - bool / object + */ + public static function getVCalendar($id, $security = true, $shared = false){ + $event_object = self::getEventObject($id, $security, $shared); + if($event_object === false){ + return false; + } + $vobject = OC_VObject::parse($event_object['calendardata']); + if(is_null($vobject)){ + return false; + } + return $vobject; + } + + /* + * @brief checks if an event was edited and dies if it was + * @param (object) $vevent - vevent object of the event + * @param (int) $lastmodified - time of last modification as unix timestamp + * @return (bool) + */ + public static function isNotModified($vevent, $lastmodified){ $last_modified = $vevent->__get('LAST-MODIFIED'); if($last_modified && $lastmodified != $last_modified->getDateTime()->format('U')){ OC_JSON::error(array('modified'=>true)); exit; } + return true; } - + + /* + * @brief returns the default categories of ownCloud + * @return (array) $categories + */ protected static function getDefaultCategories() { return array( @@ -75,14 +136,22 @@ class OC_Calendar_App{ self::$l10n->t('Work'), ); } - + + /* + * @brief returns the vcategories object of the user + * @return (object) $vcategories + */ protected static function getVCategories() { if (is_null(self::$categories)) { self::$categories = new OC_VCategories('calendar', null, self::getDefaultCategories()); } return self::$categories; } - + + /* + * @brief returns the categories of the vcategories object + * @return (array) $categories + */ public static function getCategoryOptions() { $categories = self::getVCategories()->categories(); @@ -127,40 +196,226 @@ class OC_Calendar_App{ public static function getRepeatOptions(){ return OC_Calendar_Object::getRepeatOptions(self::$l10n); } - + + /* + * @brief returns the options for the end of an repeating event + * @return array - valid inputs for the end of an repeating events + */ public static function getEndOptions(){ return OC_Calendar_Object::getEndOptions(self::$l10n); } - + + /* + * @brief returns the options for an monthly repeating event + * @return array - valid inputs for monthly repeating events + */ public static function getMonthOptions(){ return OC_Calendar_Object::getMonthOptions(self::$l10n); } - + + /* + * @brief returns the options for an weekly repeating event + * @return array - valid inputs for weekly repeating events + */ public static function getWeeklyOptions(){ return OC_Calendar_Object::getWeeklyOptions(self::$l10n); } - + + /* + * @brief returns the options for an yearly repeating event + * @return array - valid inputs for yearly repeating events + */ public static function getYearOptions(){ return OC_Calendar_Object::getYearOptions(self::$l10n); } - + + /* + * @brief returns the options for an yearly repeating event which occurs on specific days of the year + * @return array - valid inputs for yearly repeating events + */ public static function getByYearDayOptions(){ return OC_Calendar_Object::getByYearDayOptions(); } - + + /* + * @brief returns the options for an yearly repeating event which occurs on specific month of the year + * @return array - valid inputs for yearly repeating events + */ public static function getByMonthOptions(){ return OC_Calendar_Object::getByMonthOptions(self::$l10n); } + /* + * @brief returns the options for an yearly repeating event which occurs on specific week numbers of the year + * @return array - valid inputs for yearly repeating events + */ public static function getByWeekNoOptions(){ return OC_Calendar_Object::getByWeekNoOptions(); } - + + /* + * @brief returns the options for an yearly or monthly repeating event which occurs on specific days of the month + * @return array - valid inputs for yearly or monthly repeating events + */ public static function getByMonthDayOptions(){ return OC_Calendar_Object::getByMonthDayOptions(); } + /* + * @brief returns the options for an monthly repeating event which occurs on specific weeks of the month + * @return array - valid inputs for monthly repeating events + */ public static function getWeekofMonth(){ return OC_Calendar_Object::getWeekofMonth(self::$l10n); } + + /* + * @brief checks the access for a calendar / an event + * @param (int) $id - id of the calendar / event + * @param (string) $type - type of the id (calendar/event) + * @return (string) $access - level of access + */ + public static function getaccess($id, $type){ + if($type == self::CALENDAR){ + $calendar = self::getCalendar($id, false, false); + if($calendar['userid'] == OC_User::getUser()){ + return 'owner'; + } + $isshared = OC_Calendar_Share::check_access(OC_User::getUser(), $id, OC_Calendar_Share::CALENDAR); + if($isshared){ + $writeaccess = OC_Calendar_Share::is_editing_allowed(OC_User::getUser(), $id, OC_Calendar_Share::CALENDAR); + if($writeaccess){ + return 'rw'; + }else{ + return 'r'; + } + }else{ + return false; + } + }elseif($type == self::EVENT){ + if(OC_Calendar_Object::getowner($id) == OC_User::getUser()){ + return 'owner'; + } + $isshared = OC_Calendar_Share::check_access(OC_User::getUser(), $id, OC_Calendar_Share::EVENT); + if($isshared){ + $writeaccess = OC_Calendar_Share::is_editing_allowed(OC_User::getUser(), $id, OC_Calendar_Share::EVENT); + if($writeaccess){ + return 'rw'; + }else{ + return 'r'; + } + }else{ + return false; + } + } + } + + /* + * @brief analyses the parameter for calendar parameter and returns the objects + * @param (string) $calendarid - calendarid + * @param (int) $start - unixtimestamp of start + * @param (int) $end - unixtimestamp of end + * @return (array) $events + */ + public static function getrequestedEvents($calendarid, $start, $end){ + $events = array(); + if($calendarid == 'shared_rw' || $_GET['calendar_id'] == 'shared_r'){ + $calendars = OC_Calendar_Share::allSharedwithuser(OC_USER::getUser(), OC_Calendar_Share::CALENDAR, 1, ($_GET['calendar_id'] == 'shared_rw')?'rw':'r'); + foreach($calendars as $calendar){ + $calendarevents = OC_Calendar_Object::allInPeriod($calendar['calendarid'], $start, $end); + $events = array_merge($events, $calendarevents); + } + $singleevents = OC_Calendar_Share::allSharedwithuser(OC_USER::getUser(), OC_Calendar_Share::EVENT, 1, ($_GET['calendar_id'] == 'shared_rw')?'rw':'r'); + foreach($singleevents as $singleevent){ + $event = OC_Calendar_Object::find($singleevent['eventid']); + $events[] = $event; + } + }else{ + $calendar_id = $_GET['calendar_id']; + if (is_numeric($calendar_id)) { + $calendar = self::getCalendar($calendar_id); + OC_Response::enableCaching(0); + OC_Response::setETagHeader($calendar['ctag']); + $events = OC_Calendar_Object::allInPeriod($calendar_id, $start, $end); + } else { + OC_Hook::emit('OC_Calendar', 'getEvents', array('calendar_id' => $calendar_id, 'events' => &$events)); + } + } + return $events; + } + + /* + * @brief generates the output for an event which will be readable for our js + * @param (mixed) $event - event object / array + * @param (int) $start - unixtimestamp of start + * @param (int) $end - unixtimestamp of end + * @return (array) $output - readable output + */ + public static function generateEventOutput($event, $start, $end){ + $output = array(); + + if(isset($event['calendardata'])){ + $object = OC_VObject::parse($event['calendardata']); + $vevent = $object->VEVENT; + }else{ + $vevent = $event['vevent']; + } + + $last_modified = @$vevent->__get('LAST-MODIFIED'); + $lastmodified = ($last_modified)?$last_modified->getDateTime()->format('U'):0; + + $output = array('id'=>(int)$event['id'], + 'title' => htmlspecialchars(($event['summary']!=NULL || $event['summary'] != '')?$event['summary']: self::$l10n->t('unnamed')), + 'description' => isset($vevent->DESCRIPTION)?htmlspecialchars($vevent->DESCRIPTION->value):'', + 'lastmodified'=>$lastmodified); + + $dtstart = $vevent->DTSTART; + $start_dt = $dtstart->getDateTime(); + $dtend = OC_Calendar_Object::getDTEndFromVEvent($vevent); + $end_dt = $dtend->getDateTime(); + + if ($dtstart->getDateType() == Sabre_VObject_Element_DateTime::DATE){ + $output['allDay'] = true; + }else{ + $output['allDay'] = false; + $start_dt->setTimezone(new DateTimeZone(self::$tz)); + $end_dt->setTimezone(new DateTimeZone(self::$tz)); + } + + if($event['repeating'] == 1){ + $duration = (double) $end_dt->format('U') - (double) $start_dt->format('U'); + $r = new When(); + $r->recur($start_dt)->rrule((string) $vevent->RRULE); + /*$r = new iCal_Repeat_Generator(array('RECUR' => $start_dt, + * 'RRULE' => (string)$vevent->RRULE + * 'RDATE' => (string)$vevent->RDATE + * 'EXRULE' => (string)$vevent->EXRULE + * 'EXDATE' => (string)$vevent->EXDATE));*/ + while($result = $r->next()){ + if($result < $start){ + continue; + } + if($result > $end){ + break; + } + if($output['allDay'] == true){ + $output['start'] = $result->format('Y-m-d'); + $output['end'] = date('Y-m-d', $result->format('U') + --$duration); + }else{ + $output['start'] = $result->format('Y-m-d H:i:s'); + $output['end'] = date('Y-m-d H:i:s', $result->format('U') + $duration); + } + } + }else{ + if($output['allDay'] == true){ + $output['start'] = $start_dt->format('Y-m-d'); + $end_dt->modify('-1 sec'); + $output['end'] = $end_dt->format('Y-m-d'); + }else{ + $output['start'] = $start_dt->format('Y-m-d H:i:s'); + $output['end'] = $end_dt->format('Y-m-d H:i:s'); + } + } + return $output; + } } diff --git a/apps/calendar/lib/attendees.php b/apps/calendar/lib/attendees.php new file mode 100644 index 0000000000..ac30e11b3b --- /dev/null +++ b/apps/calendar/lib/attendees.php @@ -0,0 +1,13 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ +/* + * This class manages Attendees for calendars + */ +class OC_Calendar_Attendees{ + +} \ No newline at end of file diff --git a/apps/calendar/lib/object.php b/apps/calendar/lib/object.php index 9b6554a3e2..ae6fce3c84 100644 --- a/apps/calendar/lib/object.php +++ b/apps/calendar/lib/object.php @@ -104,7 +104,7 @@ class OC_Calendar_Object{ $uri = 'owncloud-'.md5($data.rand().time()).'.ics'; $stmt = OC_DB::prepare( 'INSERT INTO *PREFIX*calendar_objects (calendarid,objecttype,startdate,enddate,repeating,summary,calendardata,uri,lastmodified) VALUES(?,?,?,?,?,?,?,?,?)' ); - $result = $stmt->execute(array($id,$type,$startdate,$enddate,$repeating,$summary,$data,$uri,time())); + $stmt->execute(array($id,$type,$startdate,$enddate,$repeating,$summary,$data,$uri,time())); OC_Calendar_Calendar::touchCalendar($id); @@ -123,7 +123,7 @@ class OC_Calendar_Object{ list($type,$startdate,$enddate,$summary,$repeating,$uid) = self::extractData($object); $stmt = OC_DB::prepare( 'INSERT INTO *PREFIX*calendar_objects (calendarid,objecttype,startdate,enddate,repeating,summary,calendardata,uri,lastmodified) VALUES(?,?,?,?,?,?,?,?,?)' ); - $result = $stmt->execute(array($id,$type,$startdate,$enddate,$repeating,$summary,$data,$uri,time())); + $stmt->execute(array($id,$type,$startdate,$enddate,$repeating,$summary,$data,$uri,time())); OC_Calendar_Calendar::touchCalendar($id); @@ -144,7 +144,7 @@ class OC_Calendar_Object{ list($type,$startdate,$enddate,$summary,$repeating,$uid) = self::extractData($object); $stmt = OC_DB::prepare( 'UPDATE *PREFIX*calendar_objects SET objecttype=?,startdate=?,enddate=?,repeating=?,summary=?,calendardata=?, lastmodified = ? WHERE id = ?' ); - $result = $stmt->execute(array($type,$startdate,$enddate,$repeating,$summary,$data,time(),$id)); + $stmt->execute(array($type,$startdate,$enddate,$repeating,$summary,$data,time(),$id)); OC_Calendar_Calendar::touchCalendar($oldobject['calendarid']); @@ -165,7 +165,7 @@ class OC_Calendar_Object{ list($type,$startdate,$enddate,$summary,$repeating,$uid) = self::extractData($object); $stmt = OC_DB::prepare( 'UPDATE *PREFIX*calendar_objects SET objecttype=?,startdate=?,enddate=?,repeating=?,summary=?,calendardata=?, lastmodified = ? WHERE id = ?' ); - $result = $stmt->execute(array($type,$startdate,$enddate,$repeating,$summary,$data,time(),$oldobject['id'])); + $stmt->execute(array($type,$startdate,$enddate,$repeating,$summary,$data,time(),$oldobject['id'])); OC_Calendar_Calendar::touchCalendar($oldobject['calendarid']); @@ -202,7 +202,7 @@ class OC_Calendar_Object{ public static function moveToCalendar($id, $calendarid){ $stmt = OC_DB::prepare( 'UPDATE *PREFIX*calendar_objects SET calendarid=? WHERE id = ?' ); - $result = $stmt->execute(array($calendarid,$id)); + $stmt->execute(array($calendarid,$id)); OC_Calendar_Calendar::touchCalendar($id); @@ -432,11 +432,6 @@ class OC_Calendar_Object{ $errarr['title'] = 'true'; $errnum++; } - $calendar = OC_Calendar_Calendar::find($request['calendar']); - if($calendar['userid'] != OC_User::getUser()){ - $errarr['cal'] = 'true'; - $errnum++; - } $fromday = substr($request['from'], 0, 2); $frommonth = substr($request['from'], 3, 2); @@ -461,11 +456,11 @@ class OC_Calendar_Object{ if($request['repeat'] != 'doesnotrepeat'){ if(is_nan($request['interval']) && $request['interval'] != ''){ $errarr['interval'] = 'true'; - $ernum++; + $errnum++; } if(array_key_exists('repeat', $request) && !array_key_exists($request['repeat'], self::getRepeatOptions(OC_Calendar_App::$l10n))){ $errarr['repeat'] = 'true'; - $ernum++; + $errnum++; } if(array_key_exists('advanced_month_select', $request) && !array_key_exists($request['advanced_month_select'], self::getMonthOptions(OC_Calendar_App::$l10n))){ $errarr['advanced_month_select'] = 'true'; @@ -760,8 +755,6 @@ class OC_Calendar_Object{ $vevent->setDateTime('DTSTAMP', 'now', Sabre_VObject_Property_DateTime::UTC); $vevent->setString('SUMMARY', $title); - $dtstart = new Sabre_VObject_Property_DateTime('DTSTART'); - $dtend = new Sabre_VObject_Property_DateTime('DTEND'); if($allday){ $start = new DateTime($from); $end = new DateTime($to.' +1 day'); @@ -787,4 +780,15 @@ class OC_Calendar_Object{ return $vcalendar; } + + public static function getowner($id){ + $event = self::find($id); + $cal = OC_Calendar_Calendar::find($event['calendarid']); + return $cal['userid']; + } + + public static function getCalendarid($id){ + $event = self::find($id); + return $event['calendarid']; + } } diff --git a/apps/calendar/lib/share.php b/apps/calendar/lib/share.php new file mode 100644 index 0000000000..8f91be9747 --- /dev/null +++ b/apps/calendar/lib/share.php @@ -0,0 +1,259 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ +/* + * This class manages shared calendars + */ +class OC_Calendar_Share{ + const CALENDAR = 'calendar'; + const EVENT = 'event'; + /* + * @brief: returns informations about all calendar or events which users are sharing with the user - userid + * @param: (string) $userid - id of the user + * @param: (string) $type - use const self::CALENDAR or self::EVENT + * @return: (array) $return - information about calendars + */ + public static function allSharedwithuser($userid, $type, $active=null, $permission=null){ + $group_where = self::group_sql(OC_Group::getUserGroups($userid)); + $permission_where = self::permission_sql($permission); + if($type == self::CALENDAR){ + $active_where = self::active_sql($active); + }else{ + $active_where = ''; + } + $stmt = OC_DB::prepare('SELECT * FROM *PREFIX*calendar_share_' . $type . ' WHERE ((share = ? AND sharetype = "user") ' . $group_where . ') AND owner <> ? ' . $permission_where . ' ' . $active_where); + $result = $stmt->execute(array($userid, $userid)); + $return = array(); + while( $row = $result->fetchRow()){ + $return[] = $row; + } + return $return; + } + /* + * @brief: returns all users a calendar / event is shared with + * @param: (int) id - id of the calendar / event + * @param: (string) $type - use const self::CALENDAR or self::EVENT + * @return: (array) $users - information about users a calendar / event is shared with + */ + public static function allUsersSharedwith($id, $type){ + $stmt = OC_DB::prepare('SELECT * FROM *PREFIX*calendar_share_' . $type . ' WHERE ' . $type . 'id = ? ORDER BY share'); + $result = $stmt->execute(array($id)); + $users = array(); + while( $row = $result->fetchRow()){ + $users[] = $row; + } + return $users; + } + /* + * @brief: shares a calendar / event + * @param: (string) $owner - userid of the owner + * @param: (string) $share - userid (if $sharetype == user) / groupid (if $sharetype == group) / token (if $sharetype == public) + * @param: (string) $sharetype - type of sharing (can be: user/group/public) + * @param: (string) $id - id of the calendar / event + * @param: (string) $type - use const self::CALENDAR or self::EVENT + * @return (mixed) - token (if $sharetype == public) / bool (if $sharetype != public) + */ + public static function share($owner, $share, $sharetype, $id, $type){ + if(self::is_already_shared($owner, $share, $sharetype, $id, $type)){ + return false; + } + switch($sharetype){ + case 'user': + case 'group': + case 'public': + break; + default: + return false; + } + if($sharetype == 'public'){ + $share = self::generate_token($id, $type); + } + $stmt = OC_DB::prepare('INSERT INTO *PREFIX*calendar_share_' . $type . ' (owner,share,sharetype,' . $type . 'id,permissions' . (($type == self::CALENDAR)?', active':'') . ') VALUES(?,?,?,?,0' . (($type == self::CALENDAR)?', 1':'') . ')' ); + $result = $stmt->execute(array($owner,$share,$sharetype,$id)); + if($sharetype == 'public'){ + return $share; + }else{ + return true; + } + } + /* + * @brief: stops sharing a calendar / event + * @param: (string) $owner - userid of the owner + * @param: (string) $share - userid (if $sharetype == user) / groupid (if $sharetype == group) / token (if $sharetype == public) + * @param: (string) $sharetype - type of sharing (can be: user/group/public) + * @param: (string) $id - id of the calendar / event + * @param: (string) $type - use const self::CALENDAR or self::EVENT + * @return (bool) + */ + public static function unshare($owner, $share, $sharetype, $id, $type){ + $stmt = OC_DB::prepare('DELETE FROM *PREFIX*calendar_share_' . $type . ' WHERE owner = ? ' . (($sharetype != 'public')?'AND share = ?':'') . ' AND sharetype = ? AND ' . $type . 'id = ?'); + if($sharetype != 'public'){ + $stmt->execute(array($owner,$share,$sharetype,$id)); + }else{ + $stmt->execute(array($owner,$sharetype,$id)); + } + return true; + } + /* + * @brief: changes the permission for a calendar / event + * @param: (string) $share - userid (if $sharetype == user) / groupid (if $sharetype == group) / token (if $sharetype == public) + * @param: (string) $sharetype - type of sharing (can be: user/group/public) + * @param: (string) $id - id of the calendar / event + * @param: (int) $permission - permission of user the calendar / event is shared with (if $sharetype == public then $permission = 0) + * @param: (string) $type - use const self::CALENDAR or self::EVENT + * @return (bool) + */ + public static function changepermission($share, $sharetype, $id, $permission, $type){ + if($sharetype == 'public' && $permission == 1){ + $permission = 0; + } + $stmt = OC_DB::prepare('UPDATE *PREFIX*calendar_share_' . $type . ' SET permissions = ? WHERE share = ? AND sharetype = ? AND ' . $type . 'id = ?'); + $stmt->execute(array($permission, $share, $sharetype, $id)); + return true; + } + /* + * @brief: generates a token for public calendars / events + * @return: (string) $token + */ + private static function generate_token($id, $type){ + $uniqid = uniqid(); + if($type == self::CALENDAR){ + $events = OC_Calendar_Object::all($id); + $string = ''; + foreach($events as $event){ + $string .= $event['calendardata']; + } + }else{ + $string = OC_Calendar_Object::find($id); + } + $string = sha1($string['calendardata']); + $id = sha1($id); + $array = array($uniqid,$string,$id); + shuffle($array); + $string = implode('', $array); + $token = md5($string); + return substr($token, rand(0,16), 15); + } + /* + * @brief: checks if it is already shared + * @param: (string) $owner - userid of the owner + * @param: (string) $share - userid (if $sharetype == user) / groupid (if $sharetype == group) / token (if $sharetype == public) + * @param: (string) $sharetype - type of sharing (can be: user/group/public) + * @param: (string) $id - id of the calendar / event + * @param: (string) $type - use const self::CALENDAR or self::EVENT + * @return (bool) + */ + public static function is_already_shared($owner, $share, $sharetype, $id, $type){ + $stmt = OC_DB::prepare('SELECT * FROM *PREFIX*calendar_share_' . $type . ' WHERE owner = ? AND share = ? AND sharetype = ? AND ' . $type . 'id = ?'); + $result = $stmt->execute(array($owner, $share, $sharetype, $id)); + if($result->numRows() > 0){ + return true; + } + return false; + } + private static function group_sql($groups){ + $group_where = ''; + $i = 0; + foreach($groups as $group){ + $group_where .= ' OR '; + $group_where .= ' (share = "' . $group . '" AND sharetype = "group") '; + $i++; + } + return $group_where; + } + private static function permission_sql($permission = null){ + $permission_where = ''; + if(!is_null($permission)){ + $permission_where = ' AND permissions = '; + $permission_where .= ($permission=='rw')?'"1"':'"0"'; + } + return $permission_where; + } + private static function active_sql($active = null){ + $active_where = ''; + if(!is_null($active)){ + $active_where = 'AND active = '; + $active_where .= (!is_null($active) && $active)?'1':'0'; + } + return $active_where; + } + /* + * @brief: checks the permission for editing an event + * @param: (string) $share - userid (if $sharetype == user) / groupid (if $sharetype == group) / token (if $sharetype == public) + * @param: (string) $id - id of the calendar / event + * @param: (string) $type - use const self::CALENDAR or self::EVENT + * @return (bool) + */ + public static function is_editing_allowed($share, $id, $type){ + $group_where = self::group_sql(OC_Group::getUserGroups($share)); + $permission_where = self::permission_sql('rw'); + $stmt = OC_DB::prepare('SELECT * FROM *PREFIX*calendar_share_' . $type . ' WHERE ((share = ? AND sharetype = "user") ' . $group_where . ') ' . $permission_where); + $result = $stmt->execute(array($share)); + if($result->numRows() == 1){ + return true; + } + if($type == self::EVENT){ + $event = OC_Calendar_App::getEventObject($id, false, false); + return self::is_editing_allowed($share, $event['calendarid'], self::CALENDAR); + } + return false; + } + /* + * @brief: checks the access of + * @param: (string) $share - userid (if $sharetype == user) / groupid (if $sharetype == group) / token (if $sharetype == public) + * @param: (string) $id - id of the calendar / event + * @param: (string) $type - use const self::CALENDAR or self::EVENT + * @return (bool) + */ + public static function check_access($share, $id, $type){ + $group_where = self::group_sql(OC_Group::getUserGroups($share)); + $stmt = OC_DB::prepare('SELECT * FROM *PREFIX*calendar_share_' . $type . ' WHERE (' . $type . 'id = ? AND (share = ? AND sharetype = "user") ' . $group_where . ')'); + $result = $stmt->execute(array($id,$share)); + $rows = $result->numRows(); + if($rows > 0){ + return true; + }elseif($type == self::EVENT){ + $event = OC_Calendar_App::getEventObject($id, false, false); + return self::check_access($share, $event['calendarid'], self::CALENDAR); + }else{ + return false; + } + } + /* + * @brief: returns the calendardata of an event or a calendar + * @param: (string) $token - token which should be searched + * @return: mixed - bool if false, array with type and id if true + */ + public static function getElementByToken($token){ + $stmt_calendar = OC_DB::prepare('SELECT * FROM *PREFIX*calendar_share_' . OC_Calendar_Share::CALENDAR . ' WHERE sharetype = "public" AND share = ?'); + $result_calendar = $stmt_calendar->execute(array($token)); + $stmt_event = OC_DB::prepare('SELECT * FROM *PREFIX*calendar_share_' . OC_Calendar_Share::EVENT . ' WHERE sharetype = "public" AND share = ?'); + $result_event = $stmt_event->execute(array($token)); + $return = array(); + if($result_calendar->numRows() == 0 && $result_event->numRows() == 0){ + return false; + }elseif($result_calendar->numRows() != 0){ + $return ['type'] = 'calendar'; + $calendar = $result_calendar->fetchRow(); + $return ['id'] = $calendar['calendarid']; + }else{ + $return ['type'] = 'event'; + $event = $result_event->fetchRow(); + $return ['id'] = $event['eventid']; + } + return $return; + } + + /* + * @brief sets the active status of the calendar + * @param (string) $ + */ + public static function set_active($share, $id, $active){ + $stmt = OC_DB::prepare('UPDATE *PREFIX*calendar_share_calendar SET active = ? WHERE share = ? AND sharetype = "user" AND calendarid = ?'); + $stmt->execute(array($active, $share, $id)); + } +} \ No newline at end of file diff --git a/apps/calendar/resettimezone.php b/apps/calendar/resettimezone.php deleted file mode 100644 index 1ef9591ae3..0000000000 --- a/apps/calendar/resettimezone.php +++ /dev/null @@ -1,4 +0,0 @@ - \ No newline at end of file diff --git a/apps/calendar/share.php b/apps/calendar/share.php new file mode 100644 index 0000000000..1cc8a2ef15 --- /dev/null +++ b/apps/calendar/share.php @@ -0,0 +1,23 @@ +t("Choose active calendars"); ?>"> +

    t('Your calendars'); ?>:

    "; $tmpl = new OC_Template('calendar', 'part.choosecalendar.rowfields'); $tmpl->assign('calendar', $option_calendars[$i]); + if(OC_Calendar_Share::allUsersSharedwith($option_calendars[$i]['id'], OC_Calendar_Share::CALENDAR) == array()){ + $shared = false; + }else{ + $shared = true; + } + $tmpl->assign('shared', $shared); $tmpl->printpage(); echo ""; } @@ -20,4 +27,25 @@ for($i = 0; $i < count($option_calendars); $i++){

    ">

    +

    +

    t('Shared calendars'); ?>:

    + +'; + $tmpl = new OC_Template('calendar', 'part.choosecalendar.rowfields.shared'); + $tmpl->assign('share', $share[$i]); + $tmpl->printpage(); + echo ''; +} +?>
    +' . $l->t('No shared calendars') . '

    '; +} +?> + \ No newline at end of file diff --git a/apps/calendar/templates/part.choosecalendar.rowfields.php b/apps/calendar/templates/part.choosecalendar.rowfields.php index d3bf6c0501..cf85f0dc53 100644 --- a/apps/calendar/templates/part.choosecalendar.rowfields.php +++ b/apps/calendar/templates/part.choosecalendar.rowfields.php @@ -1,4 +1,8 @@ "; - echo ""; - echo "t("CalDav Link") . "\" class=\"action\">t("Download") . "\" class=\"action\">t("Edit") . "\" class=\"action\" onclick=\"Calendar.UI.Calendar.edit(this, " . $_['calendar']["id"] . ");\">t("Delete") . "\" class=\"action\">"; +echo ''; +echo ''; +echo ''; +echo ''; +echo ''; +echo ''; +echo ''; \ No newline at end of file diff --git a/apps/calendar/templates/part.choosecalendar.rowfields.shared.php b/apps/calendar/templates/part.choosecalendar.rowfields.shared.php new file mode 100644 index 0000000000..a23266da0c --- /dev/null +++ b/apps/calendar/templates/part.choosecalendar.rowfields.shared.php @@ -0,0 +1,4 @@ +'; +echo ''; +echo '' . $l->t('shared with you by') . ' ' . $_['share']['owner'] . ''; \ No newline at end of file diff --git a/apps/calendar/templates/part.editevent.php b/apps/calendar/templates/part.editevent.php index 6e319e1b4e..58314db1a6 100644 --- a/apps/calendar/templates/part.editevent.php +++ b/apps/calendar/templates/part.editevent.php @@ -1,13 +1,13 @@
    ">
    - + inc("part.eventform"); ?>
    " onclick="Calendar.UI.validateEventForm('ajax/event/edit.php');"> " onclick="Calendar.UI.submitDeleteEventForm('ajax/event/delete.php');"> - " onclick="window.location='export.php?eventid=';"> + " onclick="window.location='export.php?eventid=';">
    diff --git a/apps/calendar/templates/part.eventform.php b/apps/calendar/templates/part.eventform.php index 3830c273a7..c63630a5a0 100644 --- a/apps/calendar/templates/part.eventform.php +++ b/apps/calendar/templates/part.eventform.php @@ -1,3 +1,19 @@ + + + +
    @@ -26,7 +42,7 @@ @@ -59,7 +75,27 @@
    t("Title");?>:   - +
    " onclick="Calendar.UI.showadvancedoptions();" id="advanced_options_button"> +
    +
    +
    t("Repeat");?>: @@ -112,7 +148,7 @@
    + t('Summary'); ?>:
    -
    - - - - - - -
    t("Location");?>: - " value="" maxlength="100" name="location" /> -
    - - - - - -
    t("Description");?>: - -
    - + +
    //Alarm
    +
    //Attendees
    + +
    + inc('share.dropdown'); ?> +
    + diff --git a/apps/calendar/templates/part.import.php b/apps/calendar/templates/part.import.php index 8f46484b42..90691a33e5 100644 --- a/apps/calendar/templates/part.import.php +++ b/apps/calendar/templates/part.import.php @@ -3,7 +3,7 @@ -

    t('Please choose the calendar'); ?> +

    t('Please choose the calendar'); ?>

    '; + echo html_select_options($_['categories'], $_['categories'], array('combine'=>true)); + echo ''; + } + ?> + +    t("Calendar");?>: + + + +   + + + + + +
    + + + + + + + + + + + + + +
    + id="allday_checkbox" name="allday" disabled="disabled"> + t("All Day Event");?> +
    t("From");?>: + +    t('at'):''; ?>    + +
    t("To");?>: + +    t('at'):''; ?>    + +
    + " onclick="Calendar.UI.showadvancedoptions();" id="advanced_options_button"> + + +
    + + + + + + +
    t("Repeat");?>: + " onclick="Calendar.UI.showadvancedoptionsforrepeating();" id="advanced_options_button">
    + +
    +
    //Alarm
    +
    //Attendees
    + + \ No newline at end of file diff --git a/apps/calendar/templates/share.dropdown.php b/apps/calendar/templates/share.dropdown.php new file mode 100644 index 0000000000..71556a6a21 --- /dev/null +++ b/apps/calendar/templates/share.dropdown.php @@ -0,0 +1,77 @@ + +t('Users');?>:
    +
    +
      + +
    • style="visibility:hidden;" title="t('Editable'); ?>">
    • + + +
    +t('Groups');?>:
    +
    +
      + +
    • style="visibility:hidden;" title="t('Editable'); ?>">
    • + + +
    +
    + >
    + +
    \ No newline at end of file diff --git a/apps/files_archive/tests/data/data.tar.gz b/apps/files_archive/tests/data/data.tar.gz new file mode 100644 index 0000000000..39f2cdada0 Binary files /dev/null and b/apps/files_archive/tests/data/data.tar.gz differ diff --git a/apps/files_archive/tests/data/data.zip b/apps/files_archive/tests/data/data.zip new file mode 100644 index 0000000000..eccef53eb4 Binary files /dev/null and b/apps/files_archive/tests/data/data.zip differ diff --git a/apps/files_archive/tests/data/lorem.txt b/apps/files_archive/tests/data/lorem.txt new file mode 100644 index 0000000000..b62c3fb2ff --- /dev/null +++ b/apps/files_archive/tests/data/lorem.txt @@ -0,0 +1,4 @@ +Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. +Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. +Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. +Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. \ No newline at end of file diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index 246d4f672d..8cf9451c63 100644 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -119,7 +119,7 @@ class OC_Crypt { */ public static function encrypt( $content, $key='') { $bf = self::getBlowfish($key); - return($bf->encrypt($content)); + return $bf->encrypt($content); } /** @@ -132,61 +132,62 @@ class OC_Crypt { */ public static function decrypt( $content, $key='') { $bf = self::getBlowfish($key); - return($bf->decrypt($content)); + $data=$bf->decrypt($content); + return rtrim($data, "\0"); } /** * @brief encryption of a file - * @param $filename - * @param $key the encryption key + * @param string $source + * @param string $target + * @param string $key the decryption key * * This function encrypts a file */ - public static function encryptfile( $filename, $key) { - $handleread = fopen($filename, "rb"); - if($handleread<>FALSE) { - $handlewrite = fopen($filename.OC_Crypt::$encription_extension, "wb"); + public static function encryptFile( $source, $target, $key='') { + $handleread = fopen($source, "rb"); + if($handleread!=FALSE) { + $handlewrite = fopen($target, "wb"); while (!feof($handleread)) { $content = fread($handleread, 8192); $enccontent=OC_CRYPT::encrypt( $content, $key); fwrite($handlewrite, $enccontent); } fclose($handlewrite); - unlink($filename); + fclose($handleread); } - fclose($handleread); } - /** - * @brief decryption of a file - * @param $filename - * @param $key the decryption key - * - * This function decrypts a file - */ - public static function decryptfile( $filename, $key) { - $handleread = fopen($filename.OC_Crypt::$encription_extension, "rb"); - if($handleread<>FALSE) { - $handlewrite = fopen($filename, "wb"); + /** + * @brief decryption of a file + * @param string $source + * @param string $target + * @param string $key the decryption key + * + * This function decrypts a file + */ + public static function decryptFile( $source, $target, $key='') { + $handleread = fopen($source, "rb"); + if($handleread!=FALSE) { + $handlewrite = fopen($target, "wb"); while (!feof($handleread)) { $content = fread($handleread, 8192); $enccontent=OC_CRYPT::decrypt( $content, $key); fwrite($handlewrite, $enccontent); } fclose($handlewrite); - unlink($filename.OC_Crypt::$encription_extension); + fclose($handleread); } - fclose($handleread); } /** * encrypt data in 8192b sized blocks */ - public static function blockEncrypt($data){ + public static function blockEncrypt($data, $key=''){ $result=''; while(strlen($data)){ - $result=self::encrypt(substr($data,0,8192)); + $result.=self::encrypt(substr($data,0,8192),$key); $data=substr($data,8192); } return $result; @@ -195,10 +196,10 @@ class OC_Crypt { /** * decrypt data in 8192b sized blocks */ - public static function blockDecrypt($data){ + public static function blockDecrypt($data, $key=''){ $result=''; while(strlen($data)){ - $result=self::decrypt(substr($data,0,8192)); + $result.=self::decrypt(substr($data,0,8192),$key); $data=substr($data,8192); } return $result; diff --git a/apps/files_encryption/lib/cryptstream.php b/apps/files_encryption/lib/cryptstream.php index 86583096f1..21fa38e4b5 100644 --- a/apps/files_encryption/lib/cryptstream.php +++ b/apps/files_encryption/lib/cryptstream.php @@ -64,29 +64,19 @@ class OC_CryptStream{ } public function stream_read($count){ - $pos=0; - $currentPos=ftell($this->source); - $offset=$currentPos%8192; - $result=''; - if($offset>0){ - if($this->meta['seekable']){ - fseek($this->source,-$offset,SEEK_CUR);//if seeking isnt supported the internal read buffer will be used - }else{ - $pos=strlen($this->readBuffer); - $result=$this->readBuffer; - } + //$count will always be 8192 https://bugs.php.net/bug.php?id=21641 + //This makes this function a lot simpler but will breake everything the moment it's fixed + if($count!=8192){ + OC_Log::write('files_encryption','php bug 21641 no longer holds, decryption will not work',OC_Log::FATAL); + die(); } - while($count>$pos){ - $data=fread($this->source,8192); - $pos+=8192; - if(strlen($data)){ - $result.=OC_Crypt::decrypt($data); - } + $data=fread($this->source,8192); + if(strlen($data)){ + $result=OC_Crypt::decrypt($data); + }else{ + $result=''; } - if(!$this->meta['seekable']){ - $this->readBuffer=substr($result,$count); - } - return substr($result,0,$count); + return $result; } public function stream_write($data){ @@ -107,8 +97,10 @@ class OC_CryptStream{ $oldPos=ftell($this->source); $encryptedBlock=fread($this->source,8192); fseek($this->source,$oldPos); - $block=OC_Crypt::decrypt($encryptedBlock); - $data.=substr($block,strlen($data)); + if($encryptedBlock){ + $block=OC_Crypt::decrypt($encryptedBlock); + $data.=substr($block,strlen($data)); + } } $encrypted=OC_Crypt::encrypt(substr($data,0,8192)); fwrite($this->source,$encrypted); diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index c68df06f9f..a0de411a7b 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -28,6 +28,7 @@ class OC_FileProxy_Encryption extends OC_FileProxy{ private static $blackList=null; //mimetypes blacklisted from encryption private static $metaData=array(); //metadata cache + private static $enableEncryption=null; /** * check if a file should be encrypted during write @@ -35,6 +36,12 @@ class OC_FileProxy_Encryption extends OC_FileProxy{ * @return bool */ private static function shouldEncrypt($path){ + if(is_null($this->enableEncryption)){ + $this->enableEncryption=(OC_Appconfig::getValue('files_encryption','enabled','true')=='true'); + } + if(!$this->enableEncryption){ + return false; + } if(is_null(self::$blackList)){ self::$blackList=explode(',',OC_Appconfig::getValue('files_encryption','type_blacklist','jpg,png,jpeg,avi,mpg,mpeg,mkv,mp3,oga,ogv,ogg')); } diff --git a/apps/files_encryption/tests/encryption.php b/apps/files_encryption/tests/encryption.php new file mode 100644 index 0000000000..1309325671 --- /dev/null +++ b/apps/files_encryption/tests/encryption.php @@ -0,0 +1,42 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +class Test_Encryption extends UnitTestCase { + function testEncryption(){ + $key=uniqid(); + $file=OC::$SERVERROOT.'/3rdparty/MDB2.php'; + $source=file_get_contents($file); //nice large text file + $encrypted=OC_Crypt::encrypt($source,$key); + $decrypted=OC_Crypt::decrypt($encrypted,$key); + $this->assertNotEqual($encrypted,$source); + $this->assertEqual($decrypted,$source); + + $chunk=substr($source,0,8192); + $encrypted=OC_Crypt::encrypt($chunk,$key); + $this->assertEqual(strlen($chunk),strlen($encrypted)); + $decrypted=OC_Crypt::decrypt($encrypted,$key); + $this->assertEqual($decrypted,$chunk); + + $encrypted=OC_Crypt::blockEncrypt($source,$key); + $decrypted=OC_Crypt::blockDecrypt($encrypted,$key); + $this->assertNotEqual($encrypted,$source); + $this->assertEqual($decrypted,$source); + + $tmpFileEncrypted=OC_Helper::tmpFile(); + OC_Crypt::encryptfile($file,$tmpFileEncrypted,$key); + $encrypted=file_get_contents($tmpFileEncrypted); + $decrypted=OC_Crypt::blockDecrypt($encrypted,$key); + $this->assertNotEqual($encrypted,$source); + $this->assertEqual($decrypted,$source); + + $tmpFileDecrypted=OC_Helper::tmpFile(); + OC_Crypt::decryptfile($tmpFileEncrypted,$tmpFileDecrypted,$key); + $decrypted=file_get_contents($tmpFileDecrypted); + $this->assertEqual($decrypted,$source); + } +} diff --git a/apps/files_encryption/tests/stream.php b/apps/files_encryption/tests/stream.php new file mode 100644 index 0000000000..578b091a36 --- /dev/null +++ b/apps/files_encryption/tests/stream.php @@ -0,0 +1,56 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +class Test_CryptStream extends UnitTestCase { + private $tmpFiles=array(); + + function testStream(){ + $stream=$this->getStream('test1','w'); + fwrite($stream,'foobar'); + fclose($stream); + + $stream=$this->getStream('test1','r'); + $data=fread($stream,6); + fclose($stream); + $this->assertEqual('foobar',$data); + + $file=OC::$SERVERROOT.'/3rdparty/MDB2.php'; + $source=fopen($file,'r'); + $target=$this->getStream('test2','w'); + OC_Helper::streamCopy($source,$target); + fclose($target); + fclose($source); + + $stream=$this->getStream('test2','r'); + $data=stream_get_contents($stream); + $original=file_get_contents($file); + $this->assertEqual(strlen($original),strlen($data)); + $this->assertEqual($original,$data); + } + + /** + * get a cryptstream to a temporary file + * @param string $id + * @param string $mode + * @return resource + */ + function getStream($id,$mode){ + if($id===''){ + $id=uniqid(); + } + if(!isset($this->tmpFiles[$id])){ + $file=OC_Helper::tmpFile(); + $this->tmpFiles[$id]=$file; + }else{ + $file=$this->tmpFiles[$id]; + } + $stream=fopen($file,$mode); + OC_CryptStream::$sourceStreams[$id]=array('path'=>'dummy','stream'=>$stream); + return fopen('crypt://streams/'.$id,$mode); + } +} diff --git a/apps/files_external/appinfo/app.php b/apps/files_external/appinfo/app.php index 95770b44b7..784ed032d4 100644 --- a/apps/files_external/appinfo/app.php +++ b/apps/files_external/appinfo/app.php @@ -9,3 +9,4 @@ OC::$CLASSPATH['OC_Filestorage_FTP']='apps/files_external/lib/ftp.php'; OC::$CLASSPATH['OC_Filestorage_DAV']='apps/files_external/lib/webdav.php'; OC::$CLASSPATH['OC_Filestorage_Google']='apps/files_external/lib/google.php'; +OC::$CLASSPATH['OC_Filestorage_SWIFT']='apps/files_external/lib/swift.php'; diff --git a/apps/files_external/lib/swift.php b/apps/files_external/lib/swift.php new file mode 100644 index 0000000000..e53eb1ff3b --- /dev/null +++ b/apps/files_external/lib/swift.php @@ -0,0 +1,516 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +require_once('php-cloudfiles/cloudfiles.php'); + +class OC_FileStorage_SWIFT extends OC_Filestorage_Common{ + private $host; + private $root; + private $user; + private $token; + private $secure; + /** + * @var CF_Authentication auth + */ + private $auth; + /** + * @var CF_Connection conn + */ + private $conn; + /** + * @var CF_Container rootContainer + */ + private $rootContainer; + + private static $tempFiles=array(); + + const SUBCONTAINER_FILE='.subcontainers'; + + /** + * translate directory path to container name + * @param string path + * @return string + */ + private function getContainerName($path){ + $path=trim($this->root.$path,'/'); + return md5($path); + } + + /** + * get container by path + * @param string path + * @return CF_Container + */ + private function getContainer($path){ + if($path=='' or $path=='/'){ + return $this->rootContainer; + } + try{ + $container=$this->conn->get_container($this->getContainerName($path)); + return $container; + }catch(NoSuchContainerException $e){ + return null; + } + } + + /** + * create container + * @param string path + * @return CF_Container + */ + private function createContainer($path){ + if($path=='' or $path=='/'){ + return $this->conn->create_container($this->getContainerName($path)); + } + $parent=dirname($path); + if($parent=='' or $parent=='/'){ + $parentContainer=$this->rootContainer; + }else{ + if(!$this->containerExists($parent)){ + $parentContainer=$this->createContainer($parent); + }else{ + $parentContainer=$this->getContainer($parent); + } + } + $this->addSubContainer($parentContainer,basename($path)); + return $this->conn->create_container($this->getContainerName($path)); + } + + /** + * get object by path + * @param string path + * @return CF_Object + */ + private function getObject($path){ + $container=$this->getContainer(dirname($path)); + if(is_null($container)){ + return null; + }else{ + try{ + $obj=$container->get_object(basename($path)); + return $obj; + }catch(NoSuchObjectException $e){ + return null; + } + } + } + + /** + * get the names of all objects in a container + * @param CF_Container + * @return array + */ + private function getObjects($container){ + if(is_null($container)){ + return array(); + }else{ + $files=$container->get_objects(); + foreach($files as &$file){ + $file=$file->name; + } + return $files; + } + } + + /** + * create object + * @param string path + * @return CF_Object + */ + private function createObject($path){ + $container=$this->getContainer(dirname($path)); + if(!is_null($container)){ + $container=$this->createContainer($path); + } + return $container->create_object(basename($path)); + } + + /** + * check if an object exists + * @param string + * @return bool + */ + private function objectExists($path){ + return !is_null($this->getObject($path)); + } + + /** + * check if container for path exists + * @param string path + * @return bool + */ + private function containerExists($path){ + return !is_null($this->getContainer($path)); + } + + /** + * get the list of emulated sub containers + * @param CF_Container container + * @return array + */ + private function getSubContainers($container){ + $tmpFile=OC_Helper::tmpFile(); + $obj=$this->getSubContainerFile($container); + try{ + $obj->save_to_filename($tmpFile); + }catch(Exception $e){ + return array(); + } + $obj->save_to_filename($tmpFile); + $containers=file($tmpFile); + unlink($tmpFile); + foreach($containers as &$sub){ + $sub=trim($sub); + } + return $containers; + } + + /** + * add an emulated sub container + * @param CF_Container container + * @param string name + * @return bool + */ + private function addSubContainer($container,$name){ + if(!$name){ + return false; + } + $tmpFile=OC_Helper::tmpFile(); + $obj=$this->getSubContainerFile($container); + try{ + $obj->save_to_filename($tmpFile); + $containers=file($tmpFile); + foreach($containers as &$sub){ + $sub=trim($sub); + } + if(array_search($name,$containers)!==false){ + unlink($tmpFile); + return false; + }else{ + $fh=fopen($tmpFile,'a'); + fwrite($fh,$name."\n"); + } + }catch(Exception $e){ + $containers=array(); + file_put_contents($tmpFile,$name."\n"); + } + + $obj->load_from_filename($tmpFile); + unlink($tmpFile); + return true; + } + + /** + * remove an emulated sub container + * @param CF_Container container + * @param string name + * @return bool + */ + private function removeSubContainer($container,$name){ + if(!$name){ + return false; + } + $tmpFile=OC_Helper::tmpFile(); + $obj=$this->getSubContainerFile($container); + try{ + $obj->save_to_filename($tmpFile); + $containers=file($tmpFile); + }catch(Exception $e){ + return false; + } + foreach($containers as &$sub){ + $sub=trim($sub); + } + $i=array_search($name,$containers); + if($i===false){ + unlink($tmpFile); + return false; + }else{ + unset($containers[$i]); + file_put_contents($tmpFile,implode("\n",$containers)."\n"); + } + + $obj->load_from_filename($tmpFile); + unlink($tmpFile); + return true; + } + + /** + * ensure a subcontainer file exists and return it's object + * @param CF_Container container + * @return CF_Object + */ + private function getSubContainerFile($container){ + try{ + return $container->get_object(self::SUBCONTAINER_FILE); + }catch(NoSuchObjectException $e){ + return $container->create_object(self::SUBCONTAINER_FILE); + } + } + + public function __construct($params){ + $this->token=$params['token']; + $this->host=$params['host']; + $this->user=$params['user']; + $this->root=isset($params['root'])?$params['root']:'/'; + $this->secure=isset($params['secure'])?(bool)$params['secure']:true; + if(substr($this->root,0,1)!='/'){ + $this->root='/'.$this->root; + } + $this->auth = new CF_Authentication($this->user, $this->token, null, $this->host); + $this->auth->authenticate(); + + $this->conn = new CF_Connection($this->auth); + + if(!$this->containerExists($this->root)){ + $this->rootContainer=$this->createContainer('/'); + }else{ + $this->rootContainer=$this->getContainer('/'); + } + } + + + public function mkdir($path){ + if($this->containerExists($path)){ + return false; + }else{ + $this->createContainer($path); + return true; + } + } + + public function rmdir($path){ + if(!$this->containerExists($path)){ + return false; + }else{ + $this->emptyContainer($path); + if($path!='' and $path!='/'){ + $parentContainer=$this->getContainer(dirname($path)); + $this->removeSubContainer($parentContainer,basename($path)); + } + + $this->conn->delete_container($this->getContainerName($path)); + return true; + } + } + + private function emptyContainer($path){ + $container=$this->getContainer($path); + if(is_null($container)){ + return; + } + $subContainers=$this->getSubContainers($container); + foreach($subContainers as $sub){ + if($sub){ + $this->emptyContainer($path.'/'.$sub); + $this->conn->delete_container($this->getContainerName($path.'/'.$sub)); + } + } + + $objects=$this->getObjects($container); + foreach($objects as $object){ + $container->delete_object($object); + } + } + + public function opendir($path){ + $container=$this->getContainer($path); + $files=$this->getObjects($container); + $i=array_search(self::SUBCONTAINER_FILE,$files); + if($i!==false){ + unset($files[$i]); + } + $subContainers=$this->getSubContainers($container); + $files=array_merge($files,$subContainers); + $id=$this->getContainerName($path); + OC_FakeDirStream::$dirs[$id]=$files; + return opendir('fakedir://'.$id); + } + + public function filetype($path){ + if($this->containerExists($path)){ + return 'dir'; + }else{ + return 'file'; + } + } + + public function is_readable($path){ + return true; + } + + public function is_writable($path){ + return true; + } + + public function file_exists($path){ + if($this->is_dir($path)){ + return true; + }else{ + return $this->objectExists($path); + } + } + + public function file_get_contents($path){ + $obj=$this->getObject($path); + if(is_null($obj)){ + return false; + } + return $obj->read(); + } + + public function file_put_contents($path,$content){ + $obj=$this->getObject($path); + if(is_null($obj)){ + $container=$this->getContainer(dirname($path)); + if(is_null($container)){ + return false; + } + $obj=$container->create_object(basename($path)); + } + $this->resetMTime($obj); + return $obj->write($content); + } + + public function unlink($path){ + if($this->objectExists($path)){ + $container=$this->getContainer(dirname($path)); + $container->delete_object(basename($path)); + }else{ + return false; + } + } + + public function fopen($path,$mode){ + $obj=$this->getObject($path); + if(is_null($obj)){ + return false; + } + switch($mode){ + case 'r': + case 'rb': + $fp = fopen('php://temp', 'r+'); + $obj->stream($fp); + + rewind($fp); + return $fp; + case 'w': + case 'wb': + case 'a': + case 'ab': + case 'r+': + case 'w+': + case 'wb+': + case 'a+': + case 'x': + case 'x+': + case 'c': + case 'c+': + $tmpFile=$this->getTmpFile($path); + OC_CloseStreamWrapper::$callBacks[$tmpFile]=array($this,'writeBack'); + self::$tempFiles[$tmpFile]=$path; + return fopen('close://'.$tmpFile,$mode); + } + } + + public function writeBack($tmpFile){ + if(isset(self::$tempFiles[$tmpFile])){ + $this->fromTmpFile($tmpFile,self::$tempFiles[$tmpFile]); + unlink($tmpFile); + } + } + + public function free_space($path){ + return 0; + } + + public function touch($path,$mtime=null){ + $obj=$this->getObject($path); + if(is_null($obj)){ + return false; + } + if(is_null($mtime)){ + $mtime=time(); + } + + //emulate setting mtime with metadata + $obj->metadata['Mtime']=$mtime; + $obj->sync_metadata(); + } + + public function rename($path1,$path2){ + $sourceContainer=$this->getContainer(dirname($path1)); + $targetContainer=$this->getContainer(dirname($path2)); + $result=$sourceContainer->move_object_to(basename($path1),$targetContainer,basename($path2)); + if($result){ + $targetObj=$this->getObject($path2); + $this->resetMTime($targetObj); + } + return $result; + } + + public function copy($path1,$path2){ + $sourceContainer=$this->getContainer(dirname($path1)); + $targetContainer=$this->getContainer(dirname($path2)); + $result=$sourceContainer->copy_object_to(basename($path1),$targetContainer,basename($path2)); + if($result){ + $targetObj=$this->getObject($path2); + $this->resetMTime($targetObj); + } + return $result; + } + + public function stat($path){ + $obj=$this->getObject($path); + if(is_null($obj)){ + return false; + } + + if(isset($obj->metadata['Mtime']) and $obj->metadata['Mtime']>-1){ + $mtime=$obj->metadata['Mtime']; + }else{ + $mtime=strtotime($obj->last_modified); + } + return array( + 'mtime'=>$mtime, + 'size'=>$obj->content_length, + 'ctime'=>-1, + ); + } + + private function getTmpFile($path){ + $obj=$this->getObject($path); + if(!is_null($obj)){ + $tmpFile=OC_Helper::tmpFile(); + $obj->save_to_filename($tmpFile); + return $tmpFile; + }else{ + return false; + } + } + + private function fromTmpFile($tmpFile,$path){ + $obj=$this->getObject($path); + if(is_null($obj)){ + $obj=$this->createObject($path); + } + $obj->load_from_filename($tmpFile); + $this->resetMTime($obj); + } + + /** + * remove custom mtime metadata + * @param CF_Object obj + */ + private function resetMTime($obj){ + if(isset($obj->metadata['Mtime'])){ + $obj->metadata['Mtime']=-1; + $obj->sync_metadata(); + } + } +} diff --git a/apps/files_external/tests/config.php b/apps/files_external/tests/config.php index fa4c74a6e2..edbbe9dfec 100644 --- a/apps/files_external/tests/config.php +++ b/apps/files_external/tests/config.php @@ -21,5 +21,12 @@ return array( 'token'=>'test', 'token_secret'=>'test', 'root'=>'/google', - ) + ), + 'swift'=>array( + 'run'=>true, + 'user'=>'test:tester', + 'token'=>'testing', + 'host'=>'localhost:8080/auth', + 'root'=>'/', + ), ); diff --git a/apps/files_external/tests/swift.php b/apps/files_external/tests/swift.php new file mode 100644 index 0000000000..f0bde6ed60 --- /dev/null +++ b/apps/files_external/tests/swift.php @@ -0,0 +1,32 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +$config=include('apps/files_external/tests/config.php'); +if(!is_array($config) or !isset($config['swift']) or !$config['swift']['run']){ + abstract class Test_Filestorage_SWIFT extends Test_FileStorage{} + return; +}else{ + class Test_Filestorage_SWIFT extends Test_FileStorage { + private $config; + private $id; + + public function setUp(){ + $id=uniqid(); + $this->config=include('apps/files_external/tests/config.php'); + $this->config['swift']['root'].='/'.$id;//make sure we have an new empty folder to work in + $this->instance=new OC_Filestorage_SWIFT($this->config['swift']); + } + + + public function tearDown(){ + $this->instance->rmdir(''); + } + + } +} + diff --git a/apps/remoteStorage/auth.php b/apps/remoteStorage/auth.php index c00f9d5555..2be648642a 100644 --- a/apps/remoteStorage/auth.php +++ b/apps/remoteStorage/auth.php @@ -77,7 +77,7 @@ if(count($pathParts) == 2 && $pathParts[0] == '') {
    diff --git a/apps/user_ldap/appinfo/app.php b/apps/user_ldap/appinfo/app.php index 4e4da56f05..113c8d6b7b 100644 --- a/apps/user_ldap/appinfo/app.php +++ b/apps/user_ldap/appinfo/app.php @@ -35,7 +35,7 @@ define('OC_USER_BACKEND_LDAP_DEFAULT_DISPLAY_NAME', 'uid'); // register user backend OC_User::useBackend( 'LDAP' ); -OC_Group::useBackend( 'LDAP' ); +OC_Group::useBackend( new OC_GROUP_LDAP() ); // add settings page to navigation $entry = array( diff --git a/apps/user_ldap/group_ldap.php b/apps/user_ldap/group_ldap.php index 92e3b53d24..e5948459dd 100644 --- a/apps/user_ldap/group_ldap.php +++ b/apps/user_ldap/group_ldap.php @@ -29,6 +29,7 @@ class OC_GROUP_LDAP extends OC_Group_Backend { public function __construct() { $this->ldapGroupFilter = OC_Appconfig::getValue('user_ldap', 'ldap_group_filter', '(objectClass=posixGroup)'); $this->ldapGroupDisplayName = OC_Appconfig::getValue('user_ldap', 'ldap_group_display_name', 'cn'); + $this->ldapGroupMemberAttr = OC_Appconfig::getValue('user_ldap', 'ldap_group_member_attr', 'memberUid'); } /** @@ -83,7 +84,7 @@ class OC_GROUP_LDAP extends OC_Group_Backend { $this->ldapGroupDisplayName.'='.$gid )); - return $this->retrieveList($filter, OC_LDAP::conf('ldapUserDisplayName')); + return $this->retrieveList($filter, $this->ldapGroupMemberAttr); } /** @@ -102,6 +103,15 @@ class OC_GROUP_LDAP extends OC_Group_Backend { } } + /** + * check if a group exists + * @param string $gid + * @return bool + */ + public function groupExists($gid){ + return in_array($gid, $this->getGroups()); + } + private function retrieveList($filter, $attr) { $list = OC_LDAP::search($filter, $attr); diff --git a/apps/user_ldap/lib_ldap.php b/apps/user_ldap/lib_ldap.php index 5188ef9402..eea4a82011 100644 --- a/apps/user_ldap/lib_ldap.php +++ b/apps/user_ldap/lib_ldap.php @@ -21,7 +21,14 @@ * */ -define(LDAP_GROUP_MEMBER_ASSOC_ATTR,'memberUid'); +define('LDAP_GROUP_MEMBER_ASSOC_ATTR','memberUid'); + +//needed to unbind, because we use OC_LDAP only statically +class OC_LDAP_DESTRUCTOR { + public function __destruct() { + OC_LDAP::destruct(); + } +} class OC_LDAP { static protected $ldapConnectionRes = false; @@ -38,14 +45,19 @@ class OC_LDAP { // user and group settings, that are needed in both backends static public $ldapUserDisplayName; - static public function init() { self::readConfiguration(); self::establishConnection(); } + static public function destruct() { + @ldap_unbind(self::$ldapConnectionRes); + } + static public function conf($key) { - $availableProperties = array('ldapUserDisplayName'); + $availableProperties = array( + 'ldapUserDisplayName', + ); if(in_array($key, $availableProperties)) { return self::$$key; @@ -143,8 +155,19 @@ class OC_LDAP { self::$ldapNoCase = OC_Appconfig::getValue('user_ldap', 'ldap_nocase', 0); self::$ldapUserDisplayName = OC_Appconfig::getValue('user_ldap', 'ldap_display_name', OC_USER_BACKEND_LDAP_DEFAULT_DISPLAY_NAME); - //TODO: sanity checking - self::$configured = true; + if( + !empty(self::$ldapHost) + && !empty(self::$ldapPort) + && ( + (!empty(self::$ldapAgentName) && !empty(self::$ldapAgentPassword)) + || ( empty(self::$ldapAgentName) && empty(self::$ldapAgentPassword)) + ) + && !empty(self::$ldapBase) + && !empty(self::$ldapUserDisplayName) + ) + { + self::$configured = true; + } } } @@ -152,6 +175,9 @@ class OC_LDAP { * Connects and Binds to LDAP */ static private function establishConnection() { + if(!self::$configured) { + return false; + } if(!self::$ldapConnectionRes) { self::$ldapConnectionRes = ldap_connect(self::$ldapHost, self::$ldapPort); if(ldap_set_option(self::$ldapConnectionRes, LDAP_OPT_PROTOCOL_VERSION, 3)) { @@ -162,7 +188,6 @@ class OC_LDAP { } } - //TODO: Check if it works. Before, it was outside the resource-condition $ldapLogin = @ldap_bind(self::$ldapConnectionRes, self::$ldapAgentName, self::$ldapAgentPassword ); if(!$ldapLogin) { return false; diff --git a/apps/user_openid/templates/nomode.php b/apps/user_openid/templates/nomode.php index f85d28cdc9..3bab4c2edd 100644 --- a/apps/user_openid/templates/nomode.php +++ b/apps/user_openid/templates/nomode.php @@ -5,7 +5,7 @@ global $profile; ?>
    - " alt="ownCloud" /> + " alt="ownCloud" />
    • diff --git a/config/config.sample.php b/config/config.sample.php index 199c9248c5..9f6d674fc0 100755 --- a/config/config.sample.php +++ b/config/config.sample.php @@ -19,6 +19,11 @@ $CONFIG = array( "knowledgebaseurl" => "", "appstoreenabled" => true, "appstoreurl" => "", +"mail_smtpmode" => "sendmail", +"mail_smtphost" => "127.0.0.1", +"mail_smtpauth" => "false", +"mail_smtpname" => "", +"mail_smtppassword" => "", // "datadirectory" => "" ); ?> diff --git a/core/css/styles.css b/core/css/styles.css index 6380a3d74c..726427b47a 100644 --- a/core/css/styles.css +++ b/core/css/styles.css @@ -17,7 +17,7 @@ body { background:#fefefe; font:normal .8em/1.6em "Lucida Grande", Arial, Verdan /* HEADERS */ #body-user #header, #body-settings #header { position:fixed; top:0; z-index:100; width:100%; height:2.5em; padding:.5em; background:#1d2d44; -moz-box-shadow:0 0 10px rgba(0, 0, 0, .5), inset 0 -2px 10px #222; -webkit-box-shadow:0 0 10px rgba(0, 0, 0, .5), inset 0 -2px 10px #222; box-shadow:0 0 10px rgba(0, 0, 0, .5), inset 0 -2px 10px #222; } -#body-login #header { margin: -2em auto 0; text-align:center; height:10em; +#body-login #header { margin: -2em auto 0; text-align:center; height:10em; padding:1em 0 .5em; -moz-box-shadow:0 0 1em rgba(0, 0, 0, .5); -webkit-box-shadow:0 0 1em rgba(0, 0, 0, .5); box-shadow:0 0 1em rgba(0, 0, 0, .5); background: #1d2d44; /* Old browsers */ background: -moz-linear-gradient(top, #35537a 0%, #1d2d42 100%); /* FF3.6+ */ @@ -33,7 +33,7 @@ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#35537a', end /* INPUTS */ input[type="text"], input[type="password"] { cursor:text; } -input, select, button, .button, #quota, div.jp-progress, .pager li a { font-size:1em; width:10em; margin:.3em; padding:.6em .5em .4em; background:#fff; color:#333; border:1px solid #ddd; -moz-box-shadow:0 1px 1px #fff, 0 2px 0 #bbb inset; -webkit-box-shadow:0 1px 1px #fff, 0 1px 0 #bbb inset; box-shadow:0 1px 1px #fff, 0 1px 0 #bbb inset; -moz-border-radius:.5em; -webkit-border-radius:.5em; border-radius:.5em; outline:none; } +input, textarea, select, button, .button, #quota, div.jp-progress, .pager li a { font-size:1em; width:10em; margin:.3em; padding:.6em .5em .4em; background:#fff; color:#333; border:1px solid #ddd; -moz-box-shadow:0 1px 1px #fff, 0 2px 0 #bbb inset; -webkit-box-shadow:0 1px 1px #fff, 0 1px 0 #bbb inset; box-shadow:0 1px 1px #fff, 0 1px 0 #bbb inset; -moz-border-radius:.5em; -webkit-border-radius:.5em; border-radius:.5em; outline:none; } input[type="text"], input[type="password"], input[type="search"] { background:#f8f8f8; color:#555; cursor:text; } input[type="text"], input[type="password"], input[type="search"] { -webkit-appearance:textfield; -moz-appearance:textfield; -webkit-box-sizing:content-box; -moz-box-sizing:content-box; box-sizing:content-box; } input[type="text"]:hover, input[type="text"]:focus, input[type="text"]:active, @@ -58,8 +58,10 @@ input[type="submit"].highlight{ background:#ffc100; border:1px solid #db0; text- #controls .button { display:inline-block; } #content { top: 3.5em; left: 12.5em; position: absolute; } #leftcontent, .leftcontent { position:fixed; overflow: auto; top:6.4em; width:20em; background:#f8f8f8; border-right:1px solid #ddd; } -#leftcontent li, .leftcontent li { background:#f8f8f8; padding:.3em .8em; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; -webkit-transition:background-color 500ms; -moz-transition:background-color 500ms; -o-transition:background-color 500ms; transition:background-color 500ms; } +#leftcontent li, .leftcontent li { background:#f8f8f8; padding:.5em .8em; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; -webkit-transition:background-color 200ms; -moz-transition:background-color 200ms; -o-transition:background-color 200ms; transition:background-color 200ms; } #leftcontent li:hover, #leftcontent li:active, #leftcontent li.active, .leftcontent li:hover, .leftcontent li:active, .leftcontent li.active { background:#eee; } +#leftcontent li.active, .leftcontent li.active { font-weight:bold; } +#leftcontent li:hover, .leftcontent li:hover { color:#333; background:#ddd; } #rightcontent, .rightcontent { position:fixed; top: 6.4em; left: 32.5em; overflow: auto } diff --git a/core/img/favicon-touch.png b/core/img/favicon-touch.png index 20af826523..cfaaa4399a 100644 Binary files a/core/img/favicon-touch.png and b/core/img/favicon-touch.png differ diff --git a/core/img/favicon-touch.svg b/core/img/favicon-touch.svg new file mode 100644 index 0000000000..6d766d3ced --- /dev/null +++ b/core/img/favicon-touch.svg @@ -0,0 +1,787 @@ + + + +image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/core/img/favicon.png b/core/img/favicon.png index a7ee766dfa..c1b1cb6546 100644 Binary files a/core/img/favicon.png and b/core/img/favicon.png differ diff --git a/core/img/favicon.svg b/core/img/favicon.svg new file mode 100644 index 0000000000..f055c32efb --- /dev/null +++ b/core/img/favicon.svg @@ -0,0 +1,796 @@ + + + +image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/core/img/icon-error.png b/core/img/icon-error.png new file mode 100644 index 0000000000..ed438a32fd Binary files /dev/null and b/core/img/icon-error.png differ diff --git a/core/img/icon-error.svg b/core/img/icon-error.svg new file mode 100644 index 0000000000..6392d819ad --- /dev/null +++ b/core/img/icon-error.svg @@ -0,0 +1,813 @@ + + + +image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/core/img/icon-sync.png b/core/img/icon-sync.png new file mode 100644 index 0000000000..99a43d4c69 Binary files /dev/null and b/core/img/icon-sync.png differ diff --git a/core/img/icon-sync.svg b/core/img/icon-sync.svg new file mode 100644 index 0000000000..f9ebec4a5b --- /dev/null +++ b/core/img/icon-sync.svg @@ -0,0 +1,815 @@ + + + +image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/core/img/icon.png b/core/img/icon.png new file mode 100644 index 0000000000..24a4b1c3e8 Binary files /dev/null and b/core/img/icon.png differ diff --git a/core/img/icon.svg b/core/img/icon.svg new file mode 100644 index 0000000000..95eac44548 --- /dev/null +++ b/core/img/icon.svg @@ -0,0 +1,821 @@ + + + +image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/core/img/logo-inverted.png b/core/img/logo-inverted.png new file mode 100644 index 0000000000..d9fd119dc1 Binary files /dev/null and b/core/img/logo-inverted.png differ diff --git a/core/img/logo-inverted.svg b/core/img/logo-inverted.svg index 427531bc7a..9ac167cc41 100644 --- a/core/img/logo-inverted.svg +++ b/core/img/logo-inverted.svg @@ -14,94 +14,97 @@ id="Layer_1" x="0px" y="0px" - width="595.275px" - height="311.111px" - viewBox="0 0 595.275 311.111" + width="250.00002" + height="118.22803" + viewBox="0 0 250.00001 118.22802" enable-background="new 0 0 595.275 311.111" xml:space="preserve" - inkscape:version="0.48.1 r9760" - sodipodi:docname="logo inverted.svg">image/svg+xml + x1="346.77341" + y1="55.888199" + x2="346.77341" + y2="339.22119" /> @@ -132,10 +135,10 @@ + inkscape:current-layer="Layer_1" + units="mm" + fit-margin-top="0" + fit-margin-left="0" + fit-margin-right="0" + fit-margin-bottom="0" + showguides="true" + inkscape:guide-bbox="true" /> - - - - - - - - - - - + inkscape:export-filename="/home/user/owncloud/core/img/logo.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + inkscape:connector-curvature="0" /> + + + + + + + + + + \ No newline at end of file diff --git a/core/img/logo-sticker.jpg b/core/img/logo-sticker.jpg new file mode 100644 index 0000000000..ad2bf63ca3 Binary files /dev/null and b/core/img/logo-sticker.jpg differ diff --git a/core/img/logo-sticker.svg b/core/img/logo-sticker.svg new file mode 100644 index 0000000000..e48f7a78c7 --- /dev/null +++ b/core/img/logo-sticker.svg @@ -0,0 +1,764 @@ + + + +image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/core/img/logo-wide.png b/core/img/logo-wide.png index b2c16a0f60..ea10828db5 100644 Binary files a/core/img/logo-wide.png and b/core/img/logo-wide.png differ diff --git a/core/img/logo-wide.svg b/core/img/logo-wide.svg index 73b37cc7aa..37fc000747 100644 --- a/core/img/logo-wide.svg +++ b/core/img/logo-wide.svg @@ -14,16 +14,16 @@ id="Layer_1" x="0px" y="0px" - width="140" + width="147.33263" height="32" - viewBox="0 0 139.99999 32" + viewBox="0 0 147.33262 32" enable-background="new 0 0 595.275 311.111" xml:space="preserve" - inkscape:version="0.48.1 r9760" + inkscape:version="0.48.2 r9819" sodipodi:docname="logo-wide.svg">image/svg+xml + inkscape:guide-bbox="true" + fit-margin-top="0" + fit-margin-left="0" + fit-margin-right="0" + fit-margin-bottom="0" /> \ No newline at end of file + id="g4811" + transform="matrix(0.49975595,0,0,0.49975595,82.761244,31.693374)"> \ No newline at end of file diff --git a/core/img/logo.png b/core/img/logo.png new file mode 100644 index 0000000000..8177c4cdba Binary files /dev/null and b/core/img/logo.png differ diff --git a/core/img/logo.svg b/core/img/logo.svg index c1df6cb715..bd928cccfa 100644 --- a/core/img/logo.svg +++ b/core/img/logo.svg @@ -14,94 +14,97 @@ id="Layer_1" x="0px" y="0px" - width="595.275px" - height="311.111px" - viewBox="0 0 595.275 311.111" + width="250.00002" + height="118.22803" + viewBox="0 0 250.00001 118.22802" enable-background="new 0 0 595.275 311.111" xml:space="preserve" - inkscape:version="0.48.1 r9760" - sodipodi:docname="owncloud-logo.svg">image/svg+xml + x1="346.77341" + y1="55.888199" + x2="346.77341" + y2="339.22119" /> @@ -132,10 +135,10 @@ + inkscape:current-layer="Layer_1" + units="mm" + fit-margin-top="0" + fit-margin-left="0" + fit-margin-right="0" + fit-margin-bottom="0" + showguides="true" + inkscape:guide-bbox="true" /> - - - - - - - - - - - - + inkscape:connector-curvature="0" /> + + + + + + + + + + \ No newline at end of file diff --git a/core/img/owncloud-logo-medium-white.png b/core/img/owncloud-logo-medium-white.png deleted file mode 100644 index d4d06fdd62..0000000000 Binary files a/core/img/owncloud-logo-medium-white.png and /dev/null differ diff --git a/core/lostpassword/index.php b/core/lostpassword/index.php index 89bb6cfa79..b6cdd601d6 100644 --- a/core/lostpassword/index.php +++ b/core/lostpassword/index.php @@ -22,7 +22,10 @@ if (isset($_POST['user'])) { $msg = $tmpl->fetchPage(); $l = OC_L10N::get('core'); $from = 'lostpassword-noreply@' . $_SERVER['HTTP_HOST']; - mail($email, $l->t('Owncloud password reset'), $msg, 'From:' . $from); + $r=mail($email, $l->t('Owncloud password reset'), $msg, 'From:' . $from); +//if($r==false) echo('error'); else echo('works!!!!!!!'); + OC_MAIL::send($email,$_POST['user'],$l->t('Owncloud password reset'),$msg,$from,'ownCloud'); + } OC_Template::printGuestPage('core/lostpassword', 'lostpassword', array('error' => false, 'requested' => true)); } else { diff --git a/core/templates/404.php b/core/templates/404.php index 13a8101034..cd4f2b40bb 100644 --- a/core/templates/404.php +++ b/core/templates/404.php @@ -10,6 +10,6 @@ if(!isset($_)){//also provide standalone error page
      • t( 'Cloud not found' ); ?>
        -

        +

      diff --git a/core/templates/layout.guest.php b/core/templates/layout.guest.php index e1f8928fc9..2bd2e20df7 100644 --- a/core/templates/layout.guest.php +++ b/core/templates/layout.guest.php @@ -28,7 +28,7 @@
      diff --git a/files/css/files.css b/files/css/files.css index cf5b35364c..fd551b2762 100644 --- a/files/css/files.css +++ b/files/css/files.css @@ -28,21 +28,22 @@ .file_upload_start { -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; filter:alpha(opacity=0); opacity:0; z-index:1; position:absolute; left:0; top:0; width:100%; cursor:pointer;} .file_upload_filename.active { border-bottom-right-radius:0 } .file_upload_filename { position: relative; z-index:100; padding-left: 0.8em; padding-right: 0.8em; cursor:pointer; border-top-left-radius:0; border-bottom-left-radius:0; } -.file_upload_filename img { position: absolute; top: 0.4em; left: 0.4em; } +.file_upload_filename img { position: absolute; top: 0.4em; left: 0.4em; -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=100)"; filter:alpha(opacity=100); opacity:1; } .file_upload_form, .file_upload_wrapper, .file_upload_start, .file_upload_filename, #file_upload_submit { cursor:pointer; } /* FILE TABLE */ #emptyfolder { position:absolute; margin:10em 0 0 10em; font-size:1.5em; font-weight:bold; color:#888; text-shadow:#fff 0 1px 0; } +.emptyfolder #new, .emptyfolder .file_upload_filename { background:#66f866; border:1px solid #5e5; -moz-box-shadow:0 1px 1px #f8f8f8, 0 1px 1px #cfc inset; -webkit-box-shadow:0 1px 1px #f8f8f8, 0 1px 1px #cfc inset; box-shadow:0 1px 1px #f8f8f8, 0 1px 1px #cfc inset; } table { position:relative; top:37px; width:100%; } tbody tr { background-color:#fff; height:2.5em; } tbody tr:hover, tbody tr:active, tbody tr.selected { background-color:#f8f8f8; } tbody tr.selected { background-color:#eee; } tbody a { color:#000; } span.extension, td.date { color:#999; } -span.extension { text-transform:lowercase; -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; filter:alpha(opacity=0); opacity:0; -webkit-transition:opacity 300ms; -moz-transition:opacity 300ms; -o-transition:opacity 300ms; transition:opacity 300ms; } -tr:hover span.extension { -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=100)"; filter:alpha(opacity=100); opacity:1; } +span.extension { text-transform:lowercase; -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=70)"; filter:alpha(opacity=70); opacity:.7; -webkit-transition:opacity 300ms; -moz-transition:opacity 300ms; -o-transition:opacity 300ms; transition:opacity 300ms; } +tr:hover span.extension { -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=100)"; filter:alpha(opacity=100); opacity:1; color:#777; } div.crumb { float:left; display:block; background:no-repeat right 0; padding:.75em 1.5em 0 1em; height:2.9em; } div.crumb:first-child { padding-left:1em; } div.crumb.last { font-weight:bold; } @@ -56,7 +57,7 @@ table th#headerSize, table td.filesize { width:3em; padding:0 1em; text-align:ri table th#headerDate, table td.date { width:11em; padding:0 .1em 0 1em; text-align:left; } table td.selection, table th.selection, table td.fileaction { width:2em; text-align:center; } table td.filename a.name { display:block; height:1.5em; vertical-align:middle; margin-left:3em; } -table tr[data-type="dir"] td.filename a.name {font-weight:bold; } +table tr[data-type="dir"] td.filename a.name span.nametext {font-weight:bold; } table td.filename a.name input, table td.filename a.name form { width:100%; cursor:text; } table td.filename a, table td.login, table td.logout, table td.download, table td.upload, table td.create, table td.delete { padding:.2em .5em .5em 0; } table td.filename .nametext, .modified { float:left; padding:.3em 0; } diff --git a/files/js/fileactions.js b/files/js/fileactions.js index 5c6dc65d49..80e918a455 100644 --- a/files/js/fileactions.js +++ b/files/js/fileactions.js @@ -53,7 +53,7 @@ FileActions={ }, display:function(parent){ FileActions.currentFile=parent; - $('#fileList .action').remove(); + $('#fileList span.fileactions, #fileList td.date a.action').remove(); var actions=FileActions.get(FileActions.getCurrentMimeType(),FileActions.getCurrentType()); var file=FileActions.getCurrentFile(); if($('tr').filterAttr('data-file',file).data('renaming')){ @@ -113,7 +113,7 @@ FileActions={ return false; }, hide:function(){ - $('#fileList span.fileactions').fadeOut(200,function(){ + $('#fileList span.fileactions, #fileList td.date a.action').fadeOut(200,function(){ $(this).remove(); }); }, diff --git a/files/templates/index.php b/files/templates/index.php index f423b96ba1..b21cf0aeb0 100644 --- a/files/templates/index.php +++ b/files/templates/index.php @@ -2,7 +2,7 @@
      -
      +
      t('New');?>
      +
      + t('Desktop and Mobile Syncing Clients');?> + t('Download');?> +
      + +
      t('Your password got changed');?>
      diff --git a/settings/trans.png b/settings/trans.png new file mode 100644 index 0000000000..e6920168bf Binary files /dev/null and b/settings/trans.png differ diff --git a/tests/lib/filestorage.php b/tests/lib/filestorage.php index 4858234a2d..b58e28cefd 100644 --- a/tests/lib/filestorage.php +++ b/tests/lib/filestorage.php @@ -68,6 +68,15 @@ abstract class Test_FileStorage extends UnitTestCase { $this->assertFalse($this->instance->file_exists('/folder')); $this->assertFalse($this->instance->rmdir('/folder'));//cant remove non existing folders + + $dh=$this->instance->opendir('/'); + $content=array(); + while($file=readdir($dh)){ + if($file!='.' and $file!='..'){ + $content[]=$file; + } + } + $this->assertEqual(array(),$content); } /**