/**
  * @dataProvider tempnamProvider
  */
 public function testTempnam($dir, $prefix, $expected_path_prefix)
 {
     $filename = SplOverride::tempnam($dir, $prefix);
     $this->assertTrue(StringUtil::startsWith($filename, $expected_path_prefix));
     $this->assertTrue(strlen($filename) > strlen($expected_path_prefix));
     $this->assertTrue(file_exists($filename));
     $this->assertEquals(0600, fileperms($filename) & 0xfff);
     $this->assertEquals(0, filesize($filename));
 }
Пример #2
0
 /**
  * Return this user's nickname.
  *
  * The nickname will be a unique, human readable identifier for this user
  * with respect to this application. It will be an email address for some
  * users, part of the email address for some users, and the federated identity
  * for federated users who have not asserted an email address.
  *
  * @return string The user's nickname.
  */
 public function getNickname()
 {
     if ($this->email != null && $this->auth_domain != null && StringUtil::endsWith($this->email, '@' . $this->auth_domain)) {
         $suffixLen = strlen($this->auth_domain) + 1;
         return substr($this->email, 0, -$suffixLen);
     } else {
         if ($this->federated_identity) {
             return $this->federated_identity;
         } else {
             return $this->email;
         }
     }
 }
 /**
  * Retrieve more directory entries from Cloud Storage.
  *
  * @access private
  */
 private function fillFileBuffer()
 {
     $headers = $this->getOAuthTokenHeader(parent::READ_SCOPE);
     if ($headers === false) {
         trigger_error("Unable to acquire OAuth token.", E_USER_WARNING);
         return false;
     }
     $query_arr = ['delimiter' => parent::DELIMITER, 'max-keys' => self::MAX_KEYS];
     if (isset($this->prefix)) {
         $query_arr['prefix'] = $this->prefix;
     }
     if (isset($this->next_marker)) {
         $query_arr['marker'] = $this->next_marker;
     }
     $query_str = http_build_query($query_arr);
     $url = $this->createObjectUrl($this->bucket_name);
     $http_response = $this->makeHttpRequest(sprintf("%s?%s", $url, $query_str), "GET", $headers);
     if (false === $http_response) {
         trigger_error("Unable to connect to Google Cloud Storage Service.", E_USER_WARNING);
         return false;
     }
     $status_code = $http_response['status_code'];
     if (HttpResponse::OK != $status_code) {
         trigger_error($this->getErrorMessage($status_code, $http_response['body']), E_USER_WARNING);
         return false;
     }
     // Extract the files into the result array.
     $xml = simplexml_load_string($http_response['body']);
     if (isset($xml->NextMarker)) {
         $this->next_marker = (string) $xml->NextMarker;
     } else {
         $this->next_marker = null;
     }
     if (is_null($this->current_file_list)) {
         $this->current_file_list = [];
     }
     $prefix_len = isset($this->prefix) ? strlen($this->prefix) : 0;
     foreach ($xml->Contents as $content) {
         $key = (string) $content->Key;
         // Skip objects end with "_$folder$" or "/" as they exist solely for
         // the purpose of representing empty directories. Since we create
         // empty direcotires using the delimiter ("/"), they will always be
         // captured in the <CommonPrefixies> section.
         if (StringUtil::endsWith($key, parent::FOLDER_SUFFIX) || StringUtil::endsWith($key, parent::DELIMITER)) {
             continue;
         }
         if ($prefix_len != 0) {
             $key = substr($key, $prefix_len);
         }
         array_push($this->current_file_list, $key);
     }
     // All "Subdirectories" are listed as <CommonPrefixes>. See
     // https://developers.google.com/storage/docs/reference-methods#getbucket
     foreach ($xml->CommonPrefixes as $common_prefixes) {
         $key = (string) $common_prefixes->Prefix;
         if ($prefix_len != 0) {
             $key = substr($key, $prefix_len);
         }
         array_push($this->current_file_list, $key);
     }
     return true;
 }
 /**
  * Extract metadata from HTTP response headers.
  */
 private static function extractMetaData($headers)
 {
     $metadata = [];
     foreach ($headers as $key => $value) {
         if (StringUtil::startsWith(strtolower($key), self::METADATA_HEADER_PREFIX)) {
             $metadata_key = substr($key, strlen(self::METADATA_HEADER_PREFIX));
             $metadata[$metadata_key] = $value;
         }
     }
     return $metadata;
 }
Пример #5
0
 /**
  * @return string A url safe value that may be used as an option to
  * <code>LogService::fetch($options)</code> to continue reading after this
  * log.
  */
 public function getOffset()
 {
     if ($this->pb->hasOffset()) {
         $offset = $this->pb->getOffset()->serializeToString();
         return StringUtil::base64UrlEncode($offset);
     }
     return false;
 }
Пример #6
0
 /**
  * Get request logs matching the given options in reverse chronological
  * order of request end time.
  *
  * @param array $options Optional associateive arrary of filters and
  * modifiers from following:
  *
  * <ul>
  *   <li>'start_time': <code>DateTime or numeric</code> The earliest
  *   completion time or last-update time for request logs. If the value is
  *   numeric it represents microseconds since Unix epoch.</li>
  *   <li>'end_time': <code>DateTime or numeric</code> The latest completion
  *   time or last-update time for request logs. If the value is numeric it
  *   represents microseconds since Unix epoch.</li>
  *   <li>'offset': <code>string</code> The url-safe offset value from a
  *   <code>RequestLog</code> to continue iterating after.</li>
  *   <li>'minimum_log_level': <code>integer</code> Only return request logs
  *   containing at least one application log of this severity or higher.
  *   Works even if include_app_logs is not <code>true</code></li>
  *   <li>'include_incomplete': <code>boolean</code> Should incomplete request
  *   logs be included. The default is <code>false</code> - only completed
  *   logs are returned</li>
  *   <li>'include_app_logs': <code>boolean</code> Should application logs be
  *   returned. The default is <code>false</code> - application logs are not
  *   returned with their containing request logs.</li>
  *   <li>'versions': <code>array</code> The versions of the default module
  *   for which to fetch request logs. Only one of 'versions' and
  *   'module_versions' can be used.</li>
  *   <li>'module_versions': <code>arrary/code> An associative array of module
  *   names to versions for which to fetch request logs.  Each module name may
  *   be mapped to either a single <code>string</code> version or an <code>
  *   array</code> of versions.</li>
  *   <li>'batch_size': <code>integer</code> The number of request logs to
  *   pre-fetch while iterating.</li>
  * </ul>
  *
  * @return Iterator The matching <code>RequestLog</code> items.
  */
 public static function fetch(array $options = [])
 {
     $request = new LogReadRequest();
     $request->setAppId(getenv('APPLICATION_ID'));
     // Required options default values - overridden by options below.
     $batch_size = 20;
     $include_app_logs = false;
     $include_incomplete = false;
     foreach ($options as $key => $value) {
         switch ($key) {
             case 'start_time':
                 if (is_numeric($value)) {
                     $usec = (double) $value;
                 } else {
                     if ($value instanceof \DateTime) {
                         $usec = self::dateTimeToUsecs($value);
                     } else {
                         self::optionTypeException($key, $value, 'DateTime or numeric');
                     }
                 }
                 $request->setStartTime($usec);
                 break;
             case 'end_time':
                 if (is_numeric($value)) {
                     $usec = (double) $value;
                 } else {
                     if ($value instanceof \DateTime) {
                         $usec = self::dateTimeToUsecs($value);
                     } else {
                         self::optionTypeException($key, $value, 'DateTime or numeric');
                     }
                 }
                 $request->setEndTime($usec);
                 break;
             case 'offset':
                 if (!is_string($value)) {
                     self::optionTypeException($key, $value, 'string');
                 }
                 $decoded = StringUtil::base64UrlDecode($value);
                 $request->mutableOffset()->parseFromString($decoded);
                 break;
             case 'minimum_log_level':
                 if (!is_int($value)) {
                     self::optionTypeException($key, $value, 'integer');
                 }
                 if ($value > self::LEVEL_CRITICAL || $value < self::LEVEL_DEBUG) {
                     throw new \InvalidArgumentException("Option 'minimum_log_level' must be from " . self::LEVEL_DEBUG . " to " . self::LEVEL_CRITICAL);
                 }
                 $request->setMinimumLogLevel($value);
                 break;
             case 'include_incomplete':
                 if (!is_bool($value)) {
                     self::optionTypeException($key, $value, 'boolean');
                 }
                 $include_incomplete = $value;
                 break;
             case 'include_app_logs':
                 if (!is_bool($value)) {
                     self::optionTypeException($key, $value, 'boolean');
                 }
                 $include_app_logs = $value;
                 break;
             case 'module_versions':
                 if (!is_array($value)) {
                     self::optionTypeException($key, $value, 'array');
                 }
                 if (isset($options['versions'])) {
                     throw new \InvalidArgumentException("Only one of 'versions' or " . "'module_versions' may be set");
                 }
                 foreach ($value as $module => $versions) {
                     if (!is_string($module)) {
                         throw new \InvalidArgumentException('Server must be a string but was ' . self::typeOrClass($module));
                     }
                     // Versions can be a single string or an array of strings.
                     if (is_array($versions)) {
                         foreach ($versions as $version) {
                             if (!is_string($version)) {
                                 throw new \InvalidArgumentException('Server version must be a string but was ' . self::typeOrClass($version));
                             }
                             $module_version = $request->addModuleVersion();
                             if ($module !== 'default') {
                                 $module_version->setModuleId($module);
                             }
                             $module_version->setVersionId($version);
                         }
                     } else {
                         if (is_string($versions)) {
                             $module_version = $request->addModuleVersion();
                             $module_version->setModuleId($module);
                             $module_version->setVersionId($versions);
                         } else {
                             throw new \InvalidArgumentException('Server version must be a string or array but was ' . self::typeOrClass($versions));
                         }
                     }
                 }
                 break;
             case 'versions':
                 if (!is_array($value)) {
                     self::optionTypeException($key, $value, 'array');
                 }
                 if (isset($options['module_versions'])) {
                     throw new \InvalidArgumentException("Only one of 'versions' or " . "'module_versions' may be set");
                 }
                 foreach ($value as $version) {
                     if (!is_string($version)) {
                         throw new \InvalidArgumentException('Version must be a string but was ' . self::typeOrClass($version));
                     }
                     if (!preg_match(self::$MAJOR_VERSION_ID_REGEX, $version)) {
                         throw new \InvalidArgumentException("Invalid version id " . htmlspecialchars($version));
                     }
                     $request->addModuleVersion()->setVersionId($version);
                 }
                 break;
             case 'batch_size':
                 if (!is_int($value)) {
                     self::optionTypeException($key, $value, 'integer');
                 }
                 if ($value > self::MAX_BATCH_SIZE || $value < 1) {
                     throw new \InvalidArgumentException('Batch size must be > 0 and <= ' . self::MAX_BATCH_SIZE);
                 }
                 $batch_size = $value;
                 break;
             default:
                 throw new \InvalidArgumentException("Invalid option " . htmlspecialchars($key));
         }
     }
     // Set required options.
     $request->setIncludeIncomplete($include_incomplete);
     $request->setIncludeAppLogs($include_app_logs);
     $request->setCount($batch_size);
     // Set version to the current version if none set explicitly.
     if ($request->getModuleVersionSize() === 0) {
         self::setDefaultModuleVersion($request);
     }
     return new RequestLogIterator($request);
 }
 /**
  * The stat function uses GET requests to the bucket to try and determine if
  * the object is a 'file' or a 'directory', by listing the contents of the
  * bucket and then matching the results against the supplied object name.
  *
  * If a file ends with "/ then Google Cloud Console will show it as a 'folder'
  * in the UI tool, so we consider an object that ends in "/" as a directory
  * as well. For backward compatibility, we also treat files with the
  * "_$folder$" suffix as folders.
  */
 public function stat()
 {
     $prefix = $this->prefix;
     if (StringUtil::endsWith($prefix, parent::DELIMITER)) {
         $prefix = substr($prefix, 0, strlen($prefix) - 1);
     }
     if (isset($prefix)) {
         $result = $this->headObject($prefix);
         if ($result !== false) {
             $mode = parent::S_IFREG;
             $mtime = $result['mtime'];
             $size = $result['size'];
         } else {
             // Object doesn't exisit, check and see if it's a directory.
             do {
                 $results = $this->listBucket($prefix);
                 if (false === $results) {
                     return false;
                 }
                 // If there are no results then we're done
                 if (empty($results)) {
                     return false;
                 }
                 // If there is an entry that contains object_name_$folder$ or
                 // object_name/ then we have a 'directory'.
                 $object_name_folder = $prefix . parent::FOLDER_SUFFIX;
                 $object_name_delimiter = $prefix . parent::DELIMITER;
                 foreach ($results as $result) {
                     if ($result['name'] === $object_name_folder || $result['name'] === $object_name_delimiter) {
                         $mode = parent::S_IFDIR;
                         break;
                     }
                 }
             } while (!isset($mode) && isset($this->next_marker));
         }
     } else {
         // We are now just checking that the bucket exists, as there was no
         // object prefix supplied
         $results = $this->listBucket();
         if ($results !== false) {
             $mode = parent::S_IFDIR;
         } else {
             return false;
         }
     }
     // If mode is not set, then there was no object that matched the criteria.
     if (!isset($mode)) {
         return false;
     }
     // If the app could stat the file, then it must be readable. As different
     // PHP internal APIs check the access mode, we'll set them all to readable.
     $mode |= parent::S_IRUSR | parent::S_IRGRP | parent::S_IROTH;
     if ($this->isBucketWritable($this->bucket_name)) {
         $mode |= parent::S_IWUSR | parent::S_IWGRP | parent::S_IWOTH;
     }
     $stat_args["mode"] = $mode;
     if (isset($mtime)) {
         $unix_time = strtotime($mtime);
         if ($unix_time !== false) {
             $stat_args["mtime"] = $unix_time;
         }
     }
     if (isset($size)) {
         $stat_args["size"] = intval($size);
     }
     return $this->createStatArray($stat_args);
 }
Пример #8
0
 /**
  * Create file with unique file name.
  *
  * @param string $dir
  *   The directory where the temporary filename will be created.
  * @param string $prefix
  *   The prefix of the generated temporary filename.
  * @return bool|string
  *   a string with the new temporary file name on success, otherwise FALSE.
  *
  * @see http://php.net/manual/en/function.tempnam.php
  */
 public static function tempnam($dir, $prefix)
 {
     // Force $dir into a VFS temp path if it's not already one.
     $temp_root = static::sys_get_temp_dir();
     if (!StringUtil::startsWith($dir, $temp_root)) {
         $dir = $temp_root . '/' . str_replace('\\', '/', $dir);
     }
     // Create all intermediate directories if needed.
     @mkdir($dir, 0777, true);
     // Generate a unique non-existing file name.
     for ($retry = 0; $retry < 10 || file_exists($filename); $retry++) {
         $filename = $dir . '/' . uniqid($prefix, true);
     }
     if (file_exists($filename)) {
         trigger_error('Fail to generate a unique name for the temporary file', E_USER_ERROR);
         return false;
     }
     // tempnam requires the file to be created with permission set to 0600.
     if (touch($filename) === false || chmod($filename, static::DEFAULT_TMPFILE_MODE) === false) {
         trigger_error('Fail to create and change permission of temporary file ' . $filename, E_USER_ERROR);
         return false;
     }
     return $filename;
 }
Пример #9
0
 /**
  * Parse and extract the bucket and object names from the supplied filename.
  *
  * @param string $filename The filename in the format gs://bucket_name or
  * gs://bucket_name/object_name.
  * @param string &$bucket The extracted bucket.
  * @param string &$object The extracted bucket. Can be null if the filename
  * contains only bucket name.
  *
  * @return bool true if the filename is successfully parsed, false otherwise.
  */
 public static function parseFilename($filename, &$bucket, &$object)
 {
     $bucket = null;
     $object = null;
     // $filename may contain nasty characters like # and ? that can throw off
     // parse_url(). It is best to do a manual parse here.
     $gs_prefix_len = strlen(self::GS_PREFIX);
     if (!StringUtil::startsWith($filename, self::GS_PREFIX)) {
         return false;
     }
     $first_slash_pos = strpos($filename, '/', $gs_prefix_len);
     if ($first_slash_pos === false) {
         $bucket = substr($filename, $gs_prefix_len);
     } else {
         $bucket = substr($filename, $gs_prefix_len, $first_slash_pos - $gs_prefix_len);
         // gs://bucket_name/ is treated the same as gs://bucket_name where
         // $object should be set to null.
         if ($first_slash_pos != strlen($filename) - 1) {
             $object = substr($filename, $first_slash_pos);
         }
     }
     if (strlen($bucket) == 0) {
         return false;
     }
     // Validate bucket & object names.
     if (self::validateBucketName($bucket) === false) {
         trigger_error(sprintf('Invalid cloud storage bucket name \'%s\'', $bucket), E_USER_ERROR);
         return false;
     }
     if (isset($object) && self::validateObjectName($object) === false) {
         trigger_error(sprintf('Invalid cloud storage object name \'%s\'', $object), E_USER_ERROR);
         return false;
     }
     return true;
 }
Пример #10
0
 /**
  * Parse a MIME part and set the Message object accordingly.
  *
  * @param resource $part A MIME part, returned from mailparse_msg_get_part,
  *    to be parse.
  * @param string $raw_mail The string holding the raw content of the email
  *    $part is extracted from.
  * @param Message& $email The Message object to be set.
  */
 private static function parseMimePart($part, $raw_mail, &$email)
 {
     $data = mailparse_msg_get_part_data($part);
     $type = ArrayUtil::findByKeyOrDefault($data, 'content-type', 'text/plain');
     $start = $data['starting-pos-body'];
     $end = $data['ending-pos-body'];
     $encoding = ArrayUtil::findByKeyOrDefault($data, 'transfer-encoding', '');
     $content = self::decodeContent(substr($raw_mail, $start, $end - $start), $encoding);
     if (isset($data['content-disposition'])) {
         $filename = ArrayUtil::findByKeyOrDefault($data, 'disposition-filename', uniqid());
         $content_id = ArrayUtil::findByKeyOrNull($data, 'content-id');
         if ($content_id != null) {
             $content_id = "<{$content_id}>";
         }
         $email->addAttachment($filename, $content, $content_id);
     } else {
         if ($type == 'text/html') {
             $email->setHtmlBody($content);
         } else {
             if ($type == 'text/plain') {
                 $email->setTextBody($content);
             } else {
                 if (!StringUtil::startsWith($type, 'multipart/')) {
                     trigger_error("Ignore MIME part with unknown Content-Type {$type}. " . "Did you forget to specifcy Content-Disposition header?", E_USER_WARNING);
                 }
             }
         }
     }
 }
Пример #11
0
 /**
  * Parse and extract the bucket and object names from the supplied filename.
  *
  * @param string $filename The filename in the format gs://bucket_name or
  * gs://bucket_name/object_name.
  * @param string &$bucket The extracted bucket.
  * @param string &$object The extracted object. Can be null if the filename
  * contains only bucket name.
  *
  * @return bool true if the filename is successfully parsed, false otherwise.
  */
 public static function parseFilename($filename, &$bucket, &$object)
 {
     $bucket = null;
     $object = null;
     // $filename may contain nasty characters like # and ? that can throw off
     // parse_url(). It is best to do a manual parse here.
     $gs_prefix_len = strlen(self::GS_PREFIX);
     if (!StringUtil::startsWith($filename, self::GS_PREFIX)) {
         return false;
     }
     $first_slash_pos = strpos($filename, '/', $gs_prefix_len);
     if ($first_slash_pos === false) {
         $bucket = substr($filename, $gs_prefix_len);
     } else {
         $bucket = substr($filename, $gs_prefix_len, $first_slash_pos - $gs_prefix_len);
         // gs://bucket_name/ is treated the same as gs://bucket_name where
         // $object should be set to null.
         if ($first_slash_pos != strlen($filename) - 1) {
             $object = substr($filename, $first_slash_pos);
         }
     }
     if (strlen($bucket) == 0) {
         return false;
     }
     // Substitute default bucket name.
     if (ini_get('google_app_engine.gcs_default_keyword')) {
         if ($bucket === self::GS_DEFAULT_BUCKET_KEYWORD) {
             $bucket = self::getDefaultGoogleStorageBucketName();
             if (!$bucket) {
                 throw new \InvalidArgumentException('Application does not have a default Cloud Storage Bucket, ' . 'must specify a bucket name');
             }
         }
     }
     // Validate bucket & object names.
     if (self::validateBucketName($bucket) === false) {
         trigger_error(sprintf('Invalid cloud storage bucket name \'%s\'', $bucket), E_USER_ERROR);
         return false;
     }
     if (isset($object) && self::validateObjectName($object) === false) {
         trigger_error(sprintf('Invalid cloud storage object name \'%s\'', $object), E_USER_ERROR);
         return false;
     }
     return true;
 }