/** * @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)); }
/** * 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; }
/** * @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; }
/** * 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); }
/** * 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; }
/** * 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; }
/** * 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); } } } } }
/** * 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; }