3 Commits

Author SHA1 Message Date
Nathan Letwory
bced48185b Fix subtype transaction data missing.
Test plan: change type of a task, check
with transaction.search the transaction
data exists.
2020-02-27 23:35:30 +02:00
Nathan Letwory
9da2ec1e8e Merge branch 'master' into blender-tweaks-mrg
# Conflicts:
#	resources/celerity/map.php
2020-02-03 22:03:37 +02:00
Nathan Letwory
1307f0ebbe Merge branch 'master' into blender-tweaks-mrg
# Conflicts:
#	resources/celerity/map.php
2020-01-27 22:20:39 +02:00
604 changed files with 9348 additions and 28618 deletions

View File

@@ -64,13 +64,13 @@
"text": {
"type": "text",
"exclude": [
"(^src/(.*/)?__tests__/[^/]+/.*\\.(txt|json|expect))"
"(^src/(.*/)?__tests__/[^/]+/.*\\.(txt|json))"
]
},
"text-without-length": {
"type": "text",
"include": [
"(^src/(.*/)?__tests__/[^/]+/.*\\.(txt|json|expect))"
"(^src/(.*/)?__tests__/[^/]+/.*\\.(txt|json))"
],
"severity": {
"3": "disabled"

722
externals/amazon-ses/ses.php vendored Normal file
View File

@@ -0,0 +1,722 @@
<?php
/**
*
* Copyright (c) 2011, Dan Myers.
* Parts copyright (c) 2008, Donovan Schonknecht.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* This is a modified BSD license (the third clause has been removed).
* The BSD license may be found here:
* http://www.opensource.org/licenses/bsd-license.php
*
* Amazon Simple Email Service is a trademark of Amazon.com, Inc. or its affiliates.
*
* SimpleEmailService is based on Donovan Schonknecht's Amazon S3 PHP class, found here:
* http://undesigned.org.za/2007/10/22/amazon-s3-php-class
*
*/
/**
* Amazon SimpleEmailService PHP class
*
* @link http://sourceforge.net/projects/php-aws-ses/
* version 0.8.1
*
*/
class SimpleEmailService
{
protected $__accessKey; // AWS Access key
protected $__secretKey; // AWS Secret key
protected $__host;
public function getAccessKey() { return $this->__accessKey; }
public function getSecretKey() { return $this->__secretKey; }
public function getHost() { return $this->__host; }
protected $__verifyHost = 1;
protected $__verifyPeer = 1;
// verifyHost and verifyPeer determine whether curl verifies ssl certificates.
// It may be necessary to disable these checks on certain systems.
// These only have an effect if SSL is enabled.
public function verifyHost() { return $this->__verifyHost; }
public function enableVerifyHost($enable = true) { $this->__verifyHost = $enable; }
public function verifyPeer() { return $this->__verifyPeer; }
public function enableVerifyPeer($enable = true) { $this->__verifyPeer = $enable; }
// If you use exceptions, errors will be communicated by throwing a
// SimpleEmailServiceException. By default, they will be trigger_error()'d.
protected $__useExceptions = 0;
public function useExceptions() { return $this->__useExceptions; }
public function enableUseExceptions($enable = true) { $this->__useExceptions = $enable; }
/**
* Constructor
*
* @param string $accessKey Access key
* @param string $secretKey Secret key
* @return void
*/
public function __construct($accessKey = null, $secretKey = null, $host = 'email.us-east-1.amazonaws.com') {
if (!function_exists('simplexml_load_string')) {
throw new Exception(
pht(
'The PHP SimpleXML extension is not available, but this '.
'extension is required to send mail via Amazon SES, because '.
'Amazon SES returns API responses in XML format. Install or '.
'enable the SimpleXML extension.'));
}
// Catch mistakes with reading the wrong column out of the SES
// documentation. See T10728.
if (preg_match('(-smtp)', $host)) {
throw new Exception(
pht(
'Amazon SES is not configured correctly: the configured SES '.
'endpoint ("%s") is an SMTP endpoint. Instead, use an API (HTTPS) '.
'endpoint.',
$host));
}
if ($accessKey !== null && $secretKey !== null) {
$this->setAuth($accessKey, $secretKey);
}
$this->__host = $host;
}
/**
* Set AWS access key and secret key
*
* @param string $accessKey Access key
* @param string $secretKey Secret key
* @return void
*/
public function setAuth($accessKey, $secretKey) {
$this->__accessKey = $accessKey;
$this->__secretKey = $secretKey;
}
/**
* Lists the email addresses that have been verified and can be used as the 'From' address
*
* @return An array containing two items: a list of verified email addresses, and the request id.
*/
public function listVerifiedEmailAddresses() {
$rest = new SimpleEmailServiceRequest($this, 'GET');
$rest->setParameter('Action', 'ListVerifiedEmailAddresses');
$rest = $rest->getResponse();
$response = array();
if(!isset($rest->body)) {
return $response;
}
$addresses = array();
foreach($rest->body->ListVerifiedEmailAddressesResult->VerifiedEmailAddresses->member as $address) {
$addresses[] = (string)$address;
}
$response['Addresses'] = $addresses;
$response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId;
return $response;
}
/**
* Requests verification of the provided email address, so it can be used
* as the 'From' address when sending emails through SimpleEmailService.
*
* After submitting this request, you should receive a verification email
* from Amazon at the specified address containing instructions to follow.
*
* @param string email The email address to get verified
* @return The request id for this request.
*/
public function verifyEmailAddress($email) {
$rest = new SimpleEmailServiceRequest($this, 'POST');
$rest->setParameter('Action', 'VerifyEmailAddress');
$rest->setParameter('EmailAddress', $email);
$rest = $rest->getResponse();
$response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId;
return $response;
}
/**
* Removes the specified email address from the list of verified addresses.
*
* @param string email The email address to remove
* @return The request id for this request.
*/
public function deleteVerifiedEmailAddress($email) {
$rest = new SimpleEmailServiceRequest($this, 'DELETE');
$rest->setParameter('Action', 'DeleteVerifiedEmailAddress');
$rest->setParameter('EmailAddress', $email);
$rest = $rest->getResponse();
$response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId;
return $response;
}
/**
* Retrieves information on the current activity limits for this account.
* See http://docs.amazonwebservices.com/ses/latest/APIReference/API_GetSendQuota.html
*
* @return An array containing information on this account's activity limits.
*/
public function getSendQuota() {
$rest = new SimpleEmailServiceRequest($this, 'GET');
$rest->setParameter('Action', 'GetSendQuota');
$rest = $rest->getResponse();
$response = array();
if(!isset($rest->body)) {
return $response;
}
$response['Max24HourSend'] = (string)$rest->body->GetSendQuotaResult->Max24HourSend;
$response['MaxSendRate'] = (string)$rest->body->GetSendQuotaResult->MaxSendRate;
$response['SentLast24Hours'] = (string)$rest->body->GetSendQuotaResult->SentLast24Hours;
$response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId;
return $response;
}
/**
* Retrieves statistics for the last two weeks of activity on this account.
* See http://docs.amazonwebservices.com/ses/latest/APIReference/API_GetSendStatistics.html
*
* @return An array of activity statistics. Each array item covers a 15-minute period.
*/
public function getSendStatistics() {
$rest = new SimpleEmailServiceRequest($this, 'GET');
$rest->setParameter('Action', 'GetSendStatistics');
$rest = $rest->getResponse();
$response = array();
if(!isset($rest->body)) {
return $response;
}
$datapoints = array();
foreach($rest->body->GetSendStatisticsResult->SendDataPoints->member as $datapoint) {
$p = array();
$p['Bounces'] = (string)$datapoint->Bounces;
$p['Complaints'] = (string)$datapoint->Complaints;
$p['DeliveryAttempts'] = (string)$datapoint->DeliveryAttempts;
$p['Rejects'] = (string)$datapoint->Rejects;
$p['Timestamp'] = (string)$datapoint->Timestamp;
$datapoints[] = $p;
}
$response['SendDataPoints'] = $datapoints;
$response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId;
return $response;
}
public function sendRawEmail($raw) {
$rest = new SimpleEmailServiceRequest($this, 'POST');
$rest->setParameter('Action', 'SendRawEmail');
$rest->setParameter('RawMessage.Data', base64_encode($raw));
$rest = $rest->getResponse();
$response['MessageId'] = (string)$rest->body->SendEmailResult->MessageId;
$response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId;
return $response;
}
/**
* Given a SimpleEmailServiceMessage object, submits the message to the service for sending.
*
* @return An array containing the unique identifier for this message and a separate request id.
* Returns false if the provided message is missing any required fields.
*/
public function sendEmail($sesMessage) {
if(!$sesMessage->validate()) {
return false;
}
$rest = new SimpleEmailServiceRequest($this, 'POST');
$rest->setParameter('Action', 'SendEmail');
$i = 1;
foreach($sesMessage->to as $to) {
$rest->setParameter('Destination.ToAddresses.member.'.$i, $to);
$i++;
}
if(is_array($sesMessage->cc)) {
$i = 1;
foreach($sesMessage->cc as $cc) {
$rest->setParameter('Destination.CcAddresses.member.'.$i, $cc);
$i++;
}
}
if(is_array($sesMessage->bcc)) {
$i = 1;
foreach($sesMessage->bcc as $bcc) {
$rest->setParameter('Destination.BccAddresses.member.'.$i, $bcc);
$i++;
}
}
if(is_array($sesMessage->replyto)) {
$i = 1;
foreach($sesMessage->replyto as $replyto) {
$rest->setParameter('ReplyToAddresses.member.'.$i, $replyto);
$i++;
}
}
$rest->setParameter('Source', $sesMessage->from);
if($sesMessage->returnpath != null) {
$rest->setParameter('ReturnPath', $sesMessage->returnpath);
}
if($sesMessage->subject != null && strlen($sesMessage->subject) > 0) {
$rest->setParameter('Message.Subject.Data', $sesMessage->subject);
if($sesMessage->subjectCharset != null && strlen($sesMessage->subjectCharset) > 0) {
$rest->setParameter('Message.Subject.Charset', $sesMessage->subjectCharset);
}
}
if($sesMessage->messagetext != null && strlen($sesMessage->messagetext) > 0) {
$rest->setParameter('Message.Body.Text.Data', $sesMessage->messagetext);
if($sesMessage->messageTextCharset != null && strlen($sesMessage->messageTextCharset) > 0) {
$rest->setParameter('Message.Body.Text.Charset', $sesMessage->messageTextCharset);
}
}
if($sesMessage->messagehtml != null && strlen($sesMessage->messagehtml) > 0) {
$rest->setParameter('Message.Body.Html.Data', $sesMessage->messagehtml);
if($sesMessage->messageHtmlCharset != null && strlen($sesMessage->messageHtmlCharset) > 0) {
$rest->setParameter('Message.Body.Html.Charset', $sesMessage->messageHtmlCharset);
}
}
$rest = $rest->getResponse();
$response['MessageId'] = (string)$rest->body->SendEmailResult->MessageId;
$response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId;
return $response;
}
/**
* Trigger an error message
*
* @internal Used by member functions to output errors
* @param array $error Array containing error information
* @return string
*/
public function __triggerError($functionname, $error)
{
if($error == false) {
$message = sprintf("SimpleEmailService::%s(): Encountered an error, but no description given", $functionname);
}
else if(isset($error['curl']) && $error['curl'])
{
$message = sprintf("SimpleEmailService::%s(): %s %s", $functionname, $error['code'], $error['message']);
}
else if(isset($error['Error']))
{
$e = $error['Error'];
$message = sprintf("SimpleEmailService::%s(): %s - %s: %s\nRequest Id: %s\n", $functionname, $e['Type'], $e['Code'], $e['Message'], $error['RequestId']);
}
if ($this->useExceptions()) {
throw new SimpleEmailServiceException($message);
} else {
trigger_error($message, E_USER_WARNING);
}
}
/**
* Callback handler for 503 retries.
*
* @internal Used by SimpleDBRequest to call the user-specified callback, if set
* @param $attempt The number of failed attempts so far
* @return The retry delay in microseconds, or 0 to stop retrying.
*/
public function __executeServiceTemporarilyUnavailableRetryDelay($attempt)
{
if(is_callable($this->__serviceUnavailableRetryDelayCallback)) {
$callback = $this->__serviceUnavailableRetryDelayCallback;
return $callback($attempt);
}
return 0;
}
}
final class SimpleEmailServiceRequest
{
private $ses, $verb, $parameters = array();
public $response;
/**
* Constructor
*
* @param string $ses The SimpleEmailService object making this request
* @param string $action action
* @param string $verb HTTP verb
* @return mixed
*/
function __construct($ses, $verb) {
$this->ses = $ses;
$this->verb = $verb;
$this->response = new STDClass;
$this->response->error = false;
}
/**
* Set request parameter
*
* @param string $key Key
* @param string $value Value
* @param boolean $replace Whether to replace the key if it already exists (default true)
* @return void
*/
public function setParameter($key, $value, $replace = true) {
if(!$replace && isset($this->parameters[$key]))
{
$temp = (array)($this->parameters[$key]);
$temp[] = $value;
$this->parameters[$key] = $temp;
}
else
{
$this->parameters[$key] = $value;
}
}
/**
* Get the response
*
* @return object | false
*/
public function getResponse() {
$params = array();
foreach ($this->parameters as $var => $value)
{
if(is_array($value))
{
foreach($value as $v)
{
$params[] = $var.'='.$this->__customUrlEncode($v);
}
}
else
{
$params[] = $var.'='.$this->__customUrlEncode($value);
}
}
sort($params, SORT_STRING);
// must be in format 'Sun, 06 Nov 1994 08:49:37 GMT'
$date = gmdate('D, d M Y H:i:s e');
$query = implode('&', $params);
$headers = array();
$headers[] = 'Date: '.$date;
$headers[] = 'Host: '.$this->ses->getHost();
$auth = 'AWS3-HTTPS AWSAccessKeyId='.$this->ses->getAccessKey();
$auth .= ',Algorithm=HmacSHA256,Signature='.$this->__getSignature($date);
$headers[] = 'X-Amzn-Authorization: '.$auth;
$url = 'https://'.$this->ses->getHost().'/';
// Basic setup
$curl = curl_init();
curl_setopt($curl, CURLOPT_USERAGENT, 'SimpleEmailService/php');
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, ($this->ses->verifyHost() ? 2 : 0));
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, ($this->ses->verifyPeer() ? 1 : 0));
// Request types
switch ($this->verb) {
case 'GET':
$url .= '?'.$query;
break;
case 'POST':
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->verb);
curl_setopt($curl, CURLOPT_POSTFIELDS, $query);
$headers[] = 'Content-Type: application/x-www-form-urlencoded';
break;
case 'DELETE':
$url .= '?'.$query;
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'DELETE');
break;
default: break;
}
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_HEADER, false);
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, false);
curl_setopt($curl, CURLOPT_WRITEFUNCTION, array(&$this, '__responseWriteCallback'));
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
// Execute, grab errors
if (!curl_exec($curl)) {
throw new SimpleEmailServiceException(
pht(
'Encountered an error while making an HTTP request to Amazon SES '.
'(cURL Error #%d): %s',
curl_errno($curl),
curl_error($curl)));
}
$this->response->code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
if ($this->response->code != 200) {
throw new SimpleEmailServiceException(
pht(
'Unexpected HTTP status while making request to Amazon SES: '.
'expected 200, got %s.',
$this->response->code));
}
@curl_close($curl);
// Parse body into XML
if ($this->response->error === false && isset($this->response->body)) {
$this->response->body = simplexml_load_string($this->response->body);
// Grab SES errors
if (!in_array($this->response->code, array(200, 201, 202, 204))
&& isset($this->response->body->Error)) {
$error = $this->response->body->Error;
$output = array();
$output['curl'] = false;
$output['Error'] = array();
$output['Error']['Type'] = (string)$error->Type;
$output['Error']['Code'] = (string)$error->Code;
$output['Error']['Message'] = (string)$error->Message;
$output['RequestId'] = (string)$this->response->body->RequestId;
$this->response->error = $output;
unset($this->response->body);
}
}
return $this->response;
}
/**
* CURL write callback
*
* @param resource &$curl CURL resource
* @param string &$data Data
* @return integer
*/
private function __responseWriteCallback(&$curl, &$data) {
if(!isset($this->response->body)) $this->response->body = '';
$this->response->body .= $data;
return strlen($data);
}
/**
* Contributed by afx114
* URL encode the parameters as per http://docs.amazonwebservices.com/AWSECommerceService/latest/DG/index.html?Query_QueryAuth.html
* PHP's rawurlencode() follows RFC 1738, not RFC 3986 as required by Amazon. The only difference is the tilde (~), so convert it back after rawurlencode
* See: http://www.morganney.com/blog/API/AWS-Product-Advertising-API-Requires-a-Signed-Request.php
*
* @param string $var String to encode
* @return string
*/
private function __customUrlEncode($var) {
return str_replace('%7E', '~', rawurlencode($var));
}
/**
* Generate the auth string using Hmac-SHA256
*
* @internal Used by SimpleDBRequest::getResponse()
* @param string $string String to sign
* @return string
*/
private function __getSignature($string) {
return base64_encode(hash_hmac('sha256', $string, $this->ses->getSecretKey(), true));
}
}
final class SimpleEmailServiceMessage {
// these are public for convenience only
// these are not to be used outside of the SimpleEmailService class!
public $to, $cc, $bcc, $replyto;
public $from, $returnpath;
public $subject, $messagetext, $messagehtml;
public $subjectCharset, $messageTextCharset, $messageHtmlCharset;
function __construct() {
$to = array();
$cc = array();
$bcc = array();
$replyto = array();
$from = null;
$returnpath = null;
$subject = null;
$messagetext = null;
$messagehtml = null;
$subjectCharset = null;
$messageTextCharset = null;
$messageHtmlCharset = null;
}
/**
* addTo, addCC, addBCC, and addReplyTo have the following behavior:
* If a single address is passed, it is appended to the current list of addresses.
* If an array of addresses is passed, that array is merged into the current list.
*/
function addTo($to) {
if(!is_array($to)) {
$this->to[] = $to;
}
else {
$this->to = array_merge($this->to, $to);
}
}
function addCC($cc) {
if(!is_array($cc)) {
$this->cc[] = $cc;
}
else {
$this->cc = array_merge($this->cc, $cc);
}
}
function addBCC($bcc) {
if(!is_array($bcc)) {
$this->bcc[] = $bcc;
}
else {
$this->bcc = array_merge($this->bcc, $bcc);
}
}
function addReplyTo($replyto) {
if(!is_array($replyto)) {
$this->replyto[] = $replyto;
}
else {
$this->replyto = array_merge($this->replyto, $replyto);
}
}
function setFrom($from) {
$this->from = $from;
}
function setReturnPath($returnpath) {
$this->returnpath = $returnpath;
}
function setSubject($subject) {
$this->subject = $subject;
}
function setSubjectCharset($charset) {
$this->subjectCharset = $charset;
}
function setMessageFromString($text, $html = null) {
$this->messagetext = $text;
$this->messagehtml = $html;
}
function setMessageFromFile($textfile, $htmlfile = null) {
if(file_exists($textfile) && is_file($textfile) && is_readable($textfile)) {
$this->messagetext = file_get_contents($textfile);
}
if(file_exists($htmlfile) && is_file($htmlfile) && is_readable($htmlfile)) {
$this->messagehtml = file_get_contents($htmlfile);
}
}
function setMessageFromURL($texturl, $htmlurl = null) {
$this->messagetext = file_get_contents($texturl);
if($htmlurl !== null) {
$this->messagehtml = file_get_contents($htmlurl);
}
}
function setMessageCharset($textCharset, $htmlCharset = null) {
$this->messageTextCharset = $textCharset;
$this->messageHtmlCharset = $htmlCharset;
}
/**
* Validates whether the message object has sufficient information to submit a request to SES.
* This does not guarantee the message will arrive, nor that the request will succeed;
* instead, it makes sure that no required fields are missing.
*
* This is used internally before attempting a SendEmail or SendRawEmail request,
* but it can be used outside of this file if verification is desired.
* May be useful if e.g. the data is being populated from a form; developers can generally
* use this function to verify completeness instead of writing custom logic.
*
* @return boolean
*/
public function validate() {
if(count($this->to) == 0)
return false;
if($this->from == null || strlen($this->from) == 0)
return false;
if($this->messagetext == null)
return false;
return true;
}
}
/**
* Thrown by SimpleEmailService when errors occur if you call
* enableUseExceptions(true).
*/
final class SimpleEmailServiceException extends Exception {
}

View File

@@ -1,769 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE supplementalData SYSTEM "../../common/dtd/ldmlSupplemental.dtd">
<!--
Copyright © 1991-2013 Unicode, Inc.
CLDR data files are interpreted according to the LDML specification (http://unicode.org/reports/tr35/)
For terms of use, see http://www.unicode.org/copyright.html
-->
<supplementalData>
<version number="$Revision$"/>
<windowsZones>
<mapTimezones otherVersion="7e00402" typeVersion="2016i">
<!-- (UTC-12:00) International Date Line West -->
<mapZone other="Dateline Standard Time" territory="001" type="Etc/GMT+12"/>
<mapZone other="Dateline Standard Time" territory="ZZ" type="Etc/GMT+12"/>
<!-- (UTC-11:00) Coordinated Universal Time-11 -->
<mapZone other="UTC-11" territory="001" type="Etc/GMT+11"/>
<mapZone other="UTC-11" territory="AS" type="Pacific/Pago_Pago"/>
<mapZone other="UTC-11" territory="NU" type="Pacific/Niue"/>
<mapZone other="UTC-11" territory="UM" type="Pacific/Midway"/>
<mapZone other="UTC-11" territory="ZZ" type="Etc/GMT+11"/>
<!-- (UTC-10:00) Aleutian Islands -->
<mapZone other="Aleutian Standard Time" territory="001" type="America/Adak"/>
<mapZone other="Aleutian Standard Time" territory="US" type="America/Adak"/>
<!-- (UTC-10:00) Hawaii -->
<mapZone other="Hawaiian Standard Time" territory="001" type="Pacific/Honolulu"/>
<mapZone other="Hawaiian Standard Time" territory="CK" type="Pacific/Rarotonga"/>
<mapZone other="Hawaiian Standard Time" territory="PF" type="Pacific/Tahiti"/>
<mapZone other="Hawaiian Standard Time" territory="UM" type="Pacific/Johnston"/>
<mapZone other="Hawaiian Standard Time" territory="US" type="Pacific/Honolulu"/>
<mapZone other="Hawaiian Standard Time" territory="ZZ" type="Etc/GMT+10"/>
<!-- (UTC-09:30) Marquesas Islands -->
<mapZone other="Marquesas Standard Time" territory="001" type="Pacific/Marquesas"/>
<mapZone other="Marquesas Standard Time" territory="PF" type="Pacific/Marquesas"/>
<!-- (UTC-09:00) Alaska -->
<mapZone other="Alaskan Standard Time" territory="001" type="America/Anchorage"/>
<mapZone other="Alaskan Standard Time" territory="US" type="America/Anchorage America/Juneau America/Metlakatla America/Nome America/Sitka America/Yakutat"/>
<!-- (UTC-09:00) Coordinated Universal Time-09 -->
<mapZone other="UTC-09" territory="001" type="Etc/GMT+9"/>
<mapZone other="UTC-09" territory="PF" type="Pacific/Gambier"/>
<mapZone other="UTC-09" territory="ZZ" type="Etc/GMT+9"/>
<!-- (UTC-08:00) Baja California -->
<mapZone other="Pacific Standard Time (Mexico)" territory="001" type="America/Tijuana"/>
<mapZone other="Pacific Standard Time (Mexico)" territory="MX" type="America/Tijuana America/Santa_Isabel"/>
<!-- (UTC-08:00) Coordinated Universal Time-08 -->
<mapZone other="UTC-08" territory="001" type="Etc/GMT+8"/>
<mapZone other="UTC-08" territory="PN" type="Pacific/Pitcairn"/>
<mapZone other="UTC-08" territory="ZZ" type="Etc/GMT+8"/>
<!-- (UTC-08:00) Pacific Time (US & Canada) -->
<mapZone other="Pacific Standard Time" territory="001" type="America/Los_Angeles"/>
<mapZone other="Pacific Standard Time" territory="CA" type="America/Vancouver America/Dawson America/Whitehorse"/>
<mapZone other="Pacific Standard Time" territory="US" type="America/Los_Angeles"/>
<mapZone other="Pacific Standard Time" territory="ZZ" type="PST8PDT"/>
<!-- (UTC-07:00) Arizona -->
<mapZone other="US Mountain Standard Time" territory="001" type="America/Phoenix"/>
<mapZone other="US Mountain Standard Time" territory="CA" type="America/Dawson_Creek America/Creston America/Fort_Nelson"/>
<mapZone other="US Mountain Standard Time" territory="MX" type="America/Hermosillo"/>
<mapZone other="US Mountain Standard Time" territory="US" type="America/Phoenix"/>
<mapZone other="US Mountain Standard Time" territory="ZZ" type="Etc/GMT+7"/>
<!-- (UTC-07:00) Chihuahua, La Paz, Mazatlan -->
<mapZone other="Mountain Standard Time (Mexico)" territory="001" type="America/Chihuahua"/>
<mapZone other="Mountain Standard Time (Mexico)" territory="MX" type="America/Chihuahua America/Mazatlan"/>
<!-- (UTC-07:00) Mountain Time (US & Canada) -->
<mapZone other="Mountain Standard Time" territory="001" type="America/Denver"/>
<mapZone other="Mountain Standard Time" territory="CA" type="America/Edmonton America/Cambridge_Bay America/Inuvik America/Yellowknife"/>
<mapZone other="Mountain Standard Time" territory="MX" type="America/Ojinaga"/>
<mapZone other="Mountain Standard Time" territory="US" type="America/Denver America/Boise"/>
<mapZone other="Mountain Standard Time" territory="ZZ" type="MST7MDT"/>
<!-- (UTC-06:00) Central America -->
<mapZone other="Central America Standard Time" territory="001" type="America/Guatemala"/>
<mapZone other="Central America Standard Time" territory="BZ" type="America/Belize"/>
<mapZone other="Central America Standard Time" territory="CR" type="America/Costa_Rica"/>
<mapZone other="Central America Standard Time" territory="EC" type="Pacific/Galapagos"/>
<mapZone other="Central America Standard Time" territory="GT" type="America/Guatemala"/>
<mapZone other="Central America Standard Time" territory="HN" type="America/Tegucigalpa"/>
<mapZone other="Central America Standard Time" territory="NI" type="America/Managua"/>
<mapZone other="Central America Standard Time" territory="SV" type="America/El_Salvador"/>
<mapZone other="Central America Standard Time" territory="ZZ" type="Etc/GMT+6"/>
<!-- (UTC-06:00) Central Time (US & Canada) -->
<mapZone other="Central Standard Time" territory="001" type="America/Chicago"/>
<mapZone other="Central Standard Time" territory="CA" type="America/Winnipeg America/Rainy_River America/Rankin_Inlet America/Resolute"/>
<mapZone other="Central Standard Time" territory="MX" type="America/Matamoros"/>
<mapZone other="Central Standard Time" territory="US" type="America/Chicago America/Indiana/Knox America/Indiana/Tell_City America/Menominee America/North_Dakota/Beulah America/North_Dakota/Center America/North_Dakota/New_Salem"/>
<mapZone other="Central Standard Time" territory="ZZ" type="CST6CDT"/>
<!-- (UTC-06:00) Easter Island -->
<mapZone other="Easter Island Standard Time" territory="001" type="Pacific/Easter"/>
<mapZone other="Easter Island Standard Time" territory="CL" type="Pacific/Easter"/>
<!-- (UTC-06:00) Guadalajara, Mexico City, Monterrey -->
<mapZone other="Central Standard Time (Mexico)" territory="001" type="America/Mexico_City"/>
<mapZone other="Central Standard Time (Mexico)" territory="MX" type="America/Mexico_City America/Bahia_Banderas America/Merida America/Monterrey"/>
<!-- (UTC-06:00) Saskatchewan -->
<mapZone other="Canada Central Standard Time" territory="001" type="America/Regina"/>
<mapZone other="Canada Central Standard Time" territory="CA" type="America/Regina America/Swift_Current"/>
<!-- (UTC-05:00) Bogota, Lima, Quito, Rio Branco -->
<mapZone other="SA Pacific Standard Time" territory="001" type="America/Bogota"/>
<mapZone other="SA Pacific Standard Time" territory="BR" type="America/Rio_Branco America/Eirunepe"/>
<mapZone other="SA Pacific Standard Time" territory="CA" type="America/Coral_Harbour"/>
<mapZone other="SA Pacific Standard Time" territory="CO" type="America/Bogota"/>
<mapZone other="SA Pacific Standard Time" territory="EC" type="America/Guayaquil"/>
<mapZone other="SA Pacific Standard Time" territory="JM" type="America/Jamaica"/>
<mapZone other="SA Pacific Standard Time" territory="KY" type="America/Cayman"/>
<mapZone other="SA Pacific Standard Time" territory="PA" type="America/Panama"/>
<mapZone other="SA Pacific Standard Time" territory="PE" type="America/Lima"/>
<mapZone other="SA Pacific Standard Time" territory="ZZ" type="Etc/GMT+5"/>
<!-- (UTC-05:00) Chetumal -->
<mapZone other="Eastern Standard Time (Mexico)" territory="001" type="America/Cancun"/>
<mapZone other="Eastern Standard Time (Mexico)" territory="MX" type="America/Cancun"/>
<!-- (UTC-05:00) Eastern Time (US & Canada) -->
<mapZone other="Eastern Standard Time" territory="001" type="America/New_York"/>
<mapZone other="Eastern Standard Time" territory="BS" type="America/Nassau"/>
<mapZone other="Eastern Standard Time" territory="CA" type="America/Toronto America/Iqaluit America/Montreal America/Nipigon America/Pangnirtung America/Thunder_Bay"/>
<mapZone other="Eastern Standard Time" territory="US" type="America/New_York America/Detroit America/Indiana/Petersburg America/Indiana/Vincennes America/Indiana/Winamac America/Kentucky/Monticello America/Louisville"/>
<mapZone other="Eastern Standard Time" territory="ZZ" type="EST5EDT"/>
<!-- (UTC-05:00) Haiti -->
<mapZone other="Haiti Standard Time" territory="001" type="America/Port-au-Prince"/>
<mapZone other="Haiti Standard Time" territory="HT" type="America/Port-au-Prince"/>
<!-- (UTC-05:00) Havana -->
<mapZone other="Cuba Standard Time" territory="001" type="America/Havana"/>
<mapZone other="Cuba Standard Time" territory="CU" type="America/Havana"/>
<!-- (UTC-05:00) Indiana (East) -->
<mapZone other="US Eastern Standard Time" territory="001" type="America/Indianapolis"/>
<mapZone other="US Eastern Standard Time" territory="US" type="America/Indianapolis America/Indiana/Marengo America/Indiana/Vevay"/>
<!-- (UTC-04:00) Asuncion -->
<mapZone other="Paraguay Standard Time" territory="001" type="America/Asuncion"/>
<mapZone other="Paraguay Standard Time" territory="PY" type="America/Asuncion"/>
<!-- (UTC-04:00) Atlantic Time (Canada) -->
<mapZone other="Atlantic Standard Time" territory="001" type="America/Halifax"/>
<mapZone other="Atlantic Standard Time" territory="BM" type="Atlantic/Bermuda"/>
<mapZone other="Atlantic Standard Time" territory="CA" type="America/Halifax America/Glace_Bay America/Goose_Bay America/Moncton"/>
<mapZone other="Atlantic Standard Time" territory="GL" type="America/Thule"/>
<!-- (UTC-04:00) Caracas -->
<mapZone other="Venezuela Standard Time" territory="001" type="America/Caracas"/>
<mapZone other="Venezuela Standard Time" territory="VE" type="America/Caracas"/>
<!-- (UTC-04:00) Cuiaba -->
<mapZone other="Central Brazilian Standard Time" territory="001" type="America/Cuiaba"/>
<mapZone other="Central Brazilian Standard Time" territory="BR" type="America/Cuiaba America/Campo_Grande"/>
<!-- (UTC-04:00) Georgetown, La Paz, Manaus, San Juan -->
<mapZone other="SA Western Standard Time" territory="001" type="America/La_Paz"/>
<mapZone other="SA Western Standard Time" territory="AG" type="America/Antigua"/>
<mapZone other="SA Western Standard Time" territory="AI" type="America/Anguilla"/>
<mapZone other="SA Western Standard Time" territory="AW" type="America/Aruba"/>
<mapZone other="SA Western Standard Time" territory="BB" type="America/Barbados"/>
<mapZone other="SA Western Standard Time" territory="BL" type="America/St_Barthelemy"/>
<mapZone other="SA Western Standard Time" territory="BO" type="America/La_Paz"/>
<mapZone other="SA Western Standard Time" territory="BQ" type="America/Kralendijk"/>
<mapZone other="SA Western Standard Time" territory="BR" type="America/Manaus America/Boa_Vista America/Porto_Velho"/>
<mapZone other="SA Western Standard Time" territory="CA" type="America/Blanc-Sablon"/>
<mapZone other="SA Western Standard Time" territory="CW" type="America/Curacao"/>
<mapZone other="SA Western Standard Time" territory="DM" type="America/Dominica"/>
<mapZone other="SA Western Standard Time" territory="DO" type="America/Santo_Domingo"/>
<mapZone other="SA Western Standard Time" territory="GD" type="America/Grenada"/>
<mapZone other="SA Western Standard Time" territory="GP" type="America/Guadeloupe"/>
<mapZone other="SA Western Standard Time" territory="GY" type="America/Guyana"/>
<mapZone other="SA Western Standard Time" territory="KN" type="America/St_Kitts"/>
<mapZone other="SA Western Standard Time" territory="LC" type="America/St_Lucia"/>
<mapZone other="SA Western Standard Time" territory="MF" type="America/Marigot"/>
<mapZone other="SA Western Standard Time" territory="MQ" type="America/Martinique"/>
<mapZone other="SA Western Standard Time" territory="MS" type="America/Montserrat"/>
<mapZone other="SA Western Standard Time" territory="PR" type="America/Puerto_Rico"/>
<mapZone other="SA Western Standard Time" territory="SX" type="America/Lower_Princes"/>
<mapZone other="SA Western Standard Time" territory="TT" type="America/Port_of_Spain"/>
<mapZone other="SA Western Standard Time" territory="VC" type="America/St_Vincent"/>
<mapZone other="SA Western Standard Time" territory="VG" type="America/Tortola"/>
<mapZone other="SA Western Standard Time" territory="VI" type="America/St_Thomas"/>
<mapZone other="SA Western Standard Time" territory="ZZ" type="Etc/GMT+4"/>
<!-- (UTC-04:00) Santiago -->
<mapZone other="Pacific SA Standard Time" territory="001" type="America/Santiago"/>
<mapZone other="Pacific SA Standard Time" territory="AQ" type="Antarctica/Palmer"/>
<mapZone other="Pacific SA Standard Time" territory="CL" type="America/Santiago"/>
<!-- (UTC-04:00) Turks and Caicos -->
<mapZone other="Turks And Caicos Standard Time" territory="001" type="America/Grand_Turk"/>
<mapZone other="Turks And Caicos Standard Time" territory="TC" type="America/Grand_Turk"/>
<!-- (UTC-03:30) Newfoundland -->
<mapZone other="Newfoundland Standard Time" territory="001" type="America/St_Johns"/>
<mapZone other="Newfoundland Standard Time" territory="CA" type="America/St_Johns"/>
<!-- (UTC-03:00) Araguaina -->
<mapZone other="Tocantins Standard Time" territory="001" type="America/Araguaina"/>
<mapZone other="Tocantins Standard Time" territory="BR" type="America/Araguaina"/>
<!-- (UTC-03:00) Brasilia -->
<mapZone other="E. South America Standard Time" territory="001" type="America/Sao_Paulo"/>
<mapZone other="E. South America Standard Time" territory="BR" type="America/Sao_Paulo"/>
<!-- (UTC-03:00) Cayenne, Fortaleza -->
<mapZone other="SA Eastern Standard Time" territory="001" type="America/Cayenne"/>
<mapZone other="SA Eastern Standard Time" territory="AQ" type="Antarctica/Rothera"/>
<mapZone other="SA Eastern Standard Time" territory="BR" type="America/Fortaleza America/Belem America/Maceio America/Recife America/Santarem"/>
<mapZone other="SA Eastern Standard Time" territory="FK" type="Atlantic/Stanley"/>
<mapZone other="SA Eastern Standard Time" territory="GF" type="America/Cayenne"/>
<mapZone other="SA Eastern Standard Time" territory="SR" type="America/Paramaribo"/>
<mapZone other="SA Eastern Standard Time" territory="ZZ" type="Etc/GMT+3"/>
<!-- (UTC-03:00) City of Buenos Aires -->
<mapZone other="Argentina Standard Time" territory="001" type="America/Buenos_Aires"/>
<mapZone other="Argentina Standard Time" territory="AR" type="America/Buenos_Aires America/Argentina/La_Rioja America/Argentina/Rio_Gallegos America/Argentina/Salta America/Argentina/San_Juan America/Argentina/San_Luis America/Argentina/Tucuman America/Argentina/Ushuaia America/Catamarca America/Cordoba America/Jujuy America/Mendoza"/>
<!-- (UTC-03:00) Greenland -->
<mapZone other="Greenland Standard Time" territory="001" type="America/Godthab"/>
<mapZone other="Greenland Standard Time" territory="GL" type="America/Godthab"/>
<!-- (UTC-03:00) Montevideo -->
<mapZone other="Montevideo Standard Time" territory="001" type="America/Montevideo"/>
<mapZone other="Montevideo Standard Time" territory="UY" type="America/Montevideo"/>
<!-- (UTC-03:00) Saint Pierre and Miquelon -->
<mapZone other="Saint Pierre Standard Time" territory="001" type="America/Miquelon"/>
<mapZone other="Saint Pierre Standard Time" territory="PM" type="America/Miquelon"/>
<!-- (UTC-03:00) Salvador -->
<mapZone other="Bahia Standard Time" territory="001" type="America/Bahia"/>
<mapZone other="Bahia Standard Time" territory="BR" type="America/Bahia"/>
<!-- (UTC-02:00) Coordinated Universal Time-02 -->
<mapZone other="UTC-02" territory="001" type="Etc/GMT+2"/>
<mapZone other="UTC-02" territory="BR" type="America/Noronha"/>
<mapZone other="UTC-02" territory="GS" type="Atlantic/South_Georgia"/>
<mapZone other="UTC-02" territory="ZZ" type="Etc/GMT+2"/>
<!-- (UTC-01:00) Azores -->
<mapZone other="Azores Standard Time" territory="001" type="Atlantic/Azores"/>
<mapZone other="Azores Standard Time" territory="GL" type="America/Scoresbysund"/>
<mapZone other="Azores Standard Time" territory="PT" type="Atlantic/Azores"/>
<!-- (UTC-01:00) Cabo Verde Is. -->
<mapZone other="Cape Verde Standard Time" territory="001" type="Atlantic/Cape_Verde"/>
<mapZone other="Cape Verde Standard Time" territory="CV" type="Atlantic/Cape_Verde"/>
<mapZone other="Cape Verde Standard Time" territory="ZZ" type="Etc/GMT+1"/>
<!-- (UTC) Coordinated Universal Time -->
<mapZone other="UTC" territory="001" type="Etc/GMT"/>
<mapZone other="UTC" territory="GL" type="America/Danmarkshavn"/>
<mapZone other="UTC" territory="ZZ" type="Etc/GMT"/>
<!-- (UTC+00:00) Casablanca -->
<mapZone other="Morocco Standard Time" territory="001" type="Africa/Casablanca"/>
<mapZone other="Morocco Standard Time" territory="EH" type="Africa/El_Aaiun"/>
<mapZone other="Morocco Standard Time" territory="MA" type="Africa/Casablanca"/>
<!-- (UTC+00:00) Dublin, Edinburgh, Lisbon, London -->
<mapZone other="GMT Standard Time" territory="001" type="Europe/London"/>
<mapZone other="GMT Standard Time" territory="ES" type="Atlantic/Canary"/>
<mapZone other="GMT Standard Time" territory="FO" type="Atlantic/Faeroe"/>
<mapZone other="GMT Standard Time" territory="GB" type="Europe/London"/>
<mapZone other="GMT Standard Time" territory="GG" type="Europe/Guernsey"/>
<mapZone other="GMT Standard Time" territory="IE" type="Europe/Dublin"/>
<mapZone other="GMT Standard Time" territory="IM" type="Europe/Isle_of_Man"/>
<mapZone other="GMT Standard Time" territory="JE" type="Europe/Jersey"/>
<mapZone other="GMT Standard Time" territory="PT" type="Europe/Lisbon Atlantic/Madeira"/>
<!-- (UTC+00:00) Monrovia, Reykjavik -->
<mapZone other="Greenwich Standard Time" territory="001" type="Atlantic/Reykjavik"/>
<mapZone other="Greenwich Standard Time" territory="BF" type="Africa/Ouagadougou"/>
<mapZone other="Greenwich Standard Time" territory="CI" type="Africa/Abidjan"/>
<mapZone other="Greenwich Standard Time" territory="GH" type="Africa/Accra"/>
<mapZone other="Greenwich Standard Time" territory="GM" type="Africa/Banjul"/>
<mapZone other="Greenwich Standard Time" territory="GN" type="Africa/Conakry"/>
<mapZone other="Greenwich Standard Time" territory="GW" type="Africa/Bissau"/>
<mapZone other="Greenwich Standard Time" territory="IS" type="Atlantic/Reykjavik"/>
<mapZone other="Greenwich Standard Time" territory="LR" type="Africa/Monrovia"/>
<mapZone other="Greenwich Standard Time" territory="ML" type="Africa/Bamako"/>
<mapZone other="Greenwich Standard Time" territory="MR" type="Africa/Nouakchott"/>
<mapZone other="Greenwich Standard Time" territory="SH" type="Atlantic/St_Helena"/>
<mapZone other="Greenwich Standard Time" territory="SL" type="Africa/Freetown"/>
<mapZone other="Greenwich Standard Time" territory="SN" type="Africa/Dakar"/>
<mapZone other="Greenwich Standard Time" territory="ST" type="Africa/Sao_Tome"/>
<mapZone other="Greenwich Standard Time" territory="TG" type="Africa/Lome"/>
<!-- (UTC+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna -->
<mapZone other="W. Europe Standard Time" territory="001" type="Europe/Berlin"/>
<mapZone other="W. Europe Standard Time" territory="AD" type="Europe/Andorra"/>
<mapZone other="W. Europe Standard Time" territory="AT" type="Europe/Vienna"/>
<mapZone other="W. Europe Standard Time" territory="CH" type="Europe/Zurich"/>
<mapZone other="W. Europe Standard Time" territory="DE" type="Europe/Berlin Europe/Busingen"/>
<mapZone other="W. Europe Standard Time" territory="GI" type="Europe/Gibraltar"/>
<mapZone other="W. Europe Standard Time" territory="IT" type="Europe/Rome"/>
<mapZone other="W. Europe Standard Time" territory="LI" type="Europe/Vaduz"/>
<mapZone other="W. Europe Standard Time" territory="LU" type="Europe/Luxembourg"/>
<mapZone other="W. Europe Standard Time" territory="MC" type="Europe/Monaco"/>
<mapZone other="W. Europe Standard Time" territory="MT" type="Europe/Malta"/>
<mapZone other="W. Europe Standard Time" territory="NL" type="Europe/Amsterdam"/>
<mapZone other="W. Europe Standard Time" territory="NO" type="Europe/Oslo"/>
<mapZone other="W. Europe Standard Time" territory="SE" type="Europe/Stockholm"/>
<mapZone other="W. Europe Standard Time" territory="SJ" type="Arctic/Longyearbyen"/>
<mapZone other="W. Europe Standard Time" territory="SM" type="Europe/San_Marino"/>
<mapZone other="W. Europe Standard Time" territory="VA" type="Europe/Vatican"/>
<!-- (UTC+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague -->
<mapZone other="Central Europe Standard Time" territory="001" type="Europe/Budapest"/>
<mapZone other="Central Europe Standard Time" territory="AL" type="Europe/Tirane"/>
<mapZone other="Central Europe Standard Time" territory="CZ" type="Europe/Prague"/>
<mapZone other="Central Europe Standard Time" territory="HU" type="Europe/Budapest"/>
<mapZone other="Central Europe Standard Time" territory="ME" type="Europe/Podgorica"/>
<mapZone other="Central Europe Standard Time" territory="RS" type="Europe/Belgrade"/>
<mapZone other="Central Europe Standard Time" territory="SI" type="Europe/Ljubljana"/>
<mapZone other="Central Europe Standard Time" territory="SK" type="Europe/Bratislava"/>
<!-- (UTC+01:00) Brussels, Copenhagen, Madrid, Paris -->
<mapZone other="Romance Standard Time" territory="001" type="Europe/Paris"/>
<mapZone other="Romance Standard Time" territory="BE" type="Europe/Brussels"/>
<mapZone other="Romance Standard Time" territory="DK" type="Europe/Copenhagen"/>
<mapZone other="Romance Standard Time" territory="ES" type="Europe/Madrid Africa/Ceuta"/>
<mapZone other="Romance Standard Time" territory="FR" type="Europe/Paris"/>
<!-- (UTC+01:00) Sarajevo, Skopje, Warsaw, Zagreb -->
<mapZone other="Central European Standard Time" territory="001" type="Europe/Warsaw"/>
<mapZone other="Central European Standard Time" territory="BA" type="Europe/Sarajevo"/>
<mapZone other="Central European Standard Time" territory="HR" type="Europe/Zagreb"/>
<mapZone other="Central European Standard Time" territory="MK" type="Europe/Skopje"/>
<mapZone other="Central European Standard Time" territory="PL" type="Europe/Warsaw"/>
<!-- (UTC+01:00) West Central Africa -->
<mapZone other="W. Central Africa Standard Time" territory="001" type="Africa/Lagos"/>
<mapZone other="W. Central Africa Standard Time" territory="AO" type="Africa/Luanda"/>
<mapZone other="W. Central Africa Standard Time" territory="BJ" type="Africa/Porto-Novo"/>
<mapZone other="W. Central Africa Standard Time" territory="CD" type="Africa/Kinshasa"/>
<mapZone other="W. Central Africa Standard Time" territory="CF" type="Africa/Bangui"/>
<mapZone other="W. Central Africa Standard Time" territory="CG" type="Africa/Brazzaville"/>
<mapZone other="W. Central Africa Standard Time" territory="CM" type="Africa/Douala"/>
<mapZone other="W. Central Africa Standard Time" territory="DZ" type="Africa/Algiers"/>
<mapZone other="W. Central Africa Standard Time" territory="GA" type="Africa/Libreville"/>
<mapZone other="W. Central Africa Standard Time" territory="GQ" type="Africa/Malabo"/>
<mapZone other="W. Central Africa Standard Time" territory="NE" type="Africa/Niamey"/>
<mapZone other="W. Central Africa Standard Time" territory="NG" type="Africa/Lagos"/>
<mapZone other="W. Central Africa Standard Time" territory="TD" type="Africa/Ndjamena"/>
<mapZone other="W. Central Africa Standard Time" territory="TN" type="Africa/Tunis"/>
<mapZone other="W. Central Africa Standard Time" territory="ZZ" type="Etc/GMT-1"/>
<!-- (UTC+01:00) Windhoek -->
<mapZone other="Namibia Standard Time" territory="001" type="Africa/Windhoek"/>
<mapZone other="Namibia Standard Time" territory="NA" type="Africa/Windhoek"/>
<!-- (UTC+02:00) Amman -->
<mapZone other="Jordan Standard Time" territory="001" type="Asia/Amman"/>
<mapZone other="Jordan Standard Time" territory="JO" type="Asia/Amman"/>
<!-- (UTC+02:00) Athens, Bucharest -->
<mapZone other="GTB Standard Time" territory="001" type="Europe/Bucharest"/>
<mapZone other="GTB Standard Time" territory="CY" type="Asia/Nicosia"/>
<mapZone other="GTB Standard Time" territory="GR" type="Europe/Athens"/>
<mapZone other="GTB Standard Time" territory="RO" type="Europe/Bucharest"/>
<!-- (UTC+02:00) Beirut -->
<mapZone other="Middle East Standard Time" territory="001" type="Asia/Beirut"/>
<mapZone other="Middle East Standard Time" territory="LB" type="Asia/Beirut"/>
<!-- (UTC+02:00) Cairo -->
<mapZone other="Egypt Standard Time" territory="001" type="Africa/Cairo"/>
<mapZone other="Egypt Standard Time" territory="EG" type="Africa/Cairo"/>
<!-- (UTC+02:00) Chisinau -->
<mapZone other="E. Europe Standard Time" territory="001" type="Europe/Chisinau"/>
<mapZone other="E. Europe Standard Time" territory="MD" type="Europe/Chisinau"/>
<!-- (UTC+02:00) Damascus -->
<mapZone other="Syria Standard Time" territory="001" type="Asia/Damascus"/>
<mapZone other="Syria Standard Time" territory="SY" type="Asia/Damascus"/>
<!-- (UTC+02:00) Gaza, Hebron -->
<mapZone other="West Bank Standard Time" territory="001" type="Asia/Hebron"/>
<mapZone other="West Bank Standard Time" territory="PS" type="Asia/Hebron Asia/Gaza"/>
<!-- (UTC+02:00) Harare, Pretoria -->
<mapZone other="South Africa Standard Time" territory="001" type="Africa/Johannesburg"/>
<mapZone other="South Africa Standard Time" territory="BI" type="Africa/Bujumbura"/>
<mapZone other="South Africa Standard Time" territory="BW" type="Africa/Gaborone"/>
<mapZone other="South Africa Standard Time" territory="CD" type="Africa/Lubumbashi"/>
<mapZone other="South Africa Standard Time" territory="LS" type="Africa/Maseru"/>
<mapZone other="South Africa Standard Time" territory="MW" type="Africa/Blantyre"/>
<mapZone other="South Africa Standard Time" territory="MZ" type="Africa/Maputo"/>
<mapZone other="South Africa Standard Time" territory="RW" type="Africa/Kigali"/>
<mapZone other="South Africa Standard Time" territory="SZ" type="Africa/Mbabane"/>
<mapZone other="South Africa Standard Time" territory="ZA" type="Africa/Johannesburg"/>
<mapZone other="South Africa Standard Time" territory="ZM" type="Africa/Lusaka"/>
<mapZone other="South Africa Standard Time" territory="ZW" type="Africa/Harare"/>
<mapZone other="South Africa Standard Time" territory="ZZ" type="Etc/GMT-2"/>
<!-- (UTC+02:00) Helsinki, Kyiv, Riga, Sofia, Tallinn, Vilnius -->
<mapZone other="FLE Standard Time" territory="001" type="Europe/Kiev"/>
<mapZone other="FLE Standard Time" territory="AX" type="Europe/Mariehamn"/>
<mapZone other="FLE Standard Time" territory="BG" type="Europe/Sofia"/>
<mapZone other="FLE Standard Time" territory="EE" type="Europe/Tallinn"/>
<mapZone other="FLE Standard Time" territory="FI" type="Europe/Helsinki"/>
<mapZone other="FLE Standard Time" territory="LT" type="Europe/Vilnius"/>
<mapZone other="FLE Standard Time" territory="LV" type="Europe/Riga"/>
<mapZone other="FLE Standard Time" territory="UA" type="Europe/Kiev Europe/Uzhgorod Europe/Zaporozhye"/>
<!-- (UTC+02:00) Istanbul -->
<mapZone other="Turkey Standard Time" territory="001" type="Europe/Istanbul"/>
<mapZone other="Turkey Standard Time" territory="TR" type="Europe/Istanbul"/>
<!-- (UTC+02:00) Jerusalem -->
<mapZone other="Israel Standard Time" territory="001" type="Asia/Jerusalem"/>
<mapZone other="Israel Standard Time" territory="IL" type="Asia/Jerusalem"/>
<!-- (UTC+02:00) Kaliningrad -->
<mapZone other="Kaliningrad Standard Time" territory="001" type="Europe/Kaliningrad"/>
<mapZone other="Kaliningrad Standard Time" territory="RU" type="Europe/Kaliningrad"/>
<!-- (UTC+02:00) Tripoli -->
<mapZone other="Libya Standard Time" territory="001" type="Africa/Tripoli"/>
<mapZone other="Libya Standard Time" territory="LY" type="Africa/Tripoli"/>
<!-- (UTC+03:00) Baghdad -->
<mapZone other="Arabic Standard Time" territory="001" type="Asia/Baghdad"/>
<mapZone other="Arabic Standard Time" territory="IQ" type="Asia/Baghdad"/>
<!-- (UTC+03:00) Kuwait, Riyadh -->
<mapZone other="Arab Standard Time" territory="001" type="Asia/Riyadh"/>
<mapZone other="Arab Standard Time" territory="BH" type="Asia/Bahrain"/>
<mapZone other="Arab Standard Time" territory="KW" type="Asia/Kuwait"/>
<mapZone other="Arab Standard Time" territory="QA" type="Asia/Qatar"/>
<mapZone other="Arab Standard Time" territory="SA" type="Asia/Riyadh"/>
<mapZone other="Arab Standard Time" territory="YE" type="Asia/Aden"/>
<!-- (UTC+03:00) Minsk -->
<mapZone other="Belarus Standard Time" territory="001" type="Europe/Minsk"/>
<mapZone other="Belarus Standard Time" territory="BY" type="Europe/Minsk"/>
<!-- (UTC+03:00) Moscow, St. Petersburg, Volgograd -->
<mapZone other="Russian Standard Time" territory="001" type="Europe/Moscow"/>
<mapZone other="Russian Standard Time" territory="RU" type="Europe/Moscow Europe/Kirov Europe/Volgograd"/>
<mapZone other="Russian Standard Time" territory="UA" type="Europe/Simferopol"/>
<!-- (UTC+03:00) Nairobi -->
<mapZone other="E. Africa Standard Time" territory="001" type="Africa/Nairobi"/>
<mapZone other="E. Africa Standard Time" territory="AQ" type="Antarctica/Syowa"/>
<mapZone other="E. Africa Standard Time" territory="DJ" type="Africa/Djibouti"/>
<mapZone other="E. Africa Standard Time" territory="ER" type="Africa/Asmera"/>
<mapZone other="E. Africa Standard Time" territory="ET" type="Africa/Addis_Ababa"/>
<mapZone other="E. Africa Standard Time" territory="KE" type="Africa/Nairobi"/>
<mapZone other="E. Africa Standard Time" territory="KM" type="Indian/Comoro"/>
<mapZone other="E. Africa Standard Time" territory="MG" type="Indian/Antananarivo"/>
<mapZone other="E. Africa Standard Time" territory="SD" type="Africa/Khartoum"/>
<mapZone other="E. Africa Standard Time" territory="SO" type="Africa/Mogadishu"/>
<mapZone other="E. Africa Standard Time" territory="SS" type="Africa/Juba"/>
<mapZone other="E. Africa Standard Time" territory="TZ" type="Africa/Dar_es_Salaam"/>
<mapZone other="E. Africa Standard Time" territory="UG" type="Africa/Kampala"/>
<mapZone other="E. Africa Standard Time" territory="YT" type="Indian/Mayotte"/>
<mapZone other="E. Africa Standard Time" territory="ZZ" type="Etc/GMT-3"/>
<!-- (UTC+03:30) Tehran -->
<mapZone other="Iran Standard Time" territory="001" type="Asia/Tehran"/>
<mapZone other="Iran Standard Time" territory="IR" type="Asia/Tehran"/>
<!-- (UTC+04:00) Abu Dhabi, Muscat -->
<mapZone other="Arabian Standard Time" territory="001" type="Asia/Dubai"/>
<mapZone other="Arabian Standard Time" territory="AE" type="Asia/Dubai"/>
<mapZone other="Arabian Standard Time" territory="OM" type="Asia/Muscat"/>
<mapZone other="Arabian Standard Time" territory="ZZ" type="Etc/GMT-4"/>
<!-- (UTC+04:00) Astrakhan, Ulyanovsk -->
<mapZone other="Astrakhan Standard Time" territory="001" type="Europe/Astrakhan"/>
<mapZone other="Astrakhan Standard Time" territory="RU" type="Europe/Astrakhan Europe/Ulyanovsk"/>
<!-- (UTC+04:00) Baku -->
<mapZone other="Azerbaijan Standard Time" territory="001" type="Asia/Baku"/>
<mapZone other="Azerbaijan Standard Time" territory="AZ" type="Asia/Baku"/>
<!-- (UTC+04:00) Izhevsk, Samara -->
<mapZone other="Russia Time Zone 3" territory="001" type="Europe/Samara"/>
<mapZone other="Russia Time Zone 3" territory="RU" type="Europe/Samara"/>
<!-- (UTC+04:00) Port Louis -->
<mapZone other="Mauritius Standard Time" territory="001" type="Indian/Mauritius"/>
<mapZone other="Mauritius Standard Time" territory="MU" type="Indian/Mauritius"/>
<mapZone other="Mauritius Standard Time" territory="RE" type="Indian/Reunion"/>
<mapZone other="Mauritius Standard Time" territory="SC" type="Indian/Mahe"/>
<!-- (UTC+04:00) Tbilisi -->
<mapZone other="Georgian Standard Time" territory="001" type="Asia/Tbilisi"/>
<mapZone other="Georgian Standard Time" territory="GE" type="Asia/Tbilisi"/>
<!-- (UTC+04:00) Yerevan -->
<mapZone other="Caucasus Standard Time" territory="001" type="Asia/Yerevan"/>
<mapZone other="Caucasus Standard Time" territory="AM" type="Asia/Yerevan"/>
<!-- (UTC+04:30) Kabul -->
<mapZone other="Afghanistan Standard Time" territory="001" type="Asia/Kabul"/>
<mapZone other="Afghanistan Standard Time" territory="AF" type="Asia/Kabul"/>
<!-- (UTC+05:00) Ashgabat, Tashkent -->
<mapZone other="West Asia Standard Time" territory="001" type="Asia/Tashkent"/>
<mapZone other="West Asia Standard Time" territory="AQ" type="Antarctica/Mawson"/>
<mapZone other="West Asia Standard Time" territory="KZ" type="Asia/Oral Asia/Aqtau Asia/Aqtobe"/>
<mapZone other="West Asia Standard Time" territory="MV" type="Indian/Maldives"/>
<mapZone other="West Asia Standard Time" territory="TF" type="Indian/Kerguelen"/>
<mapZone other="West Asia Standard Time" territory="TJ" type="Asia/Dushanbe"/>
<mapZone other="West Asia Standard Time" territory="TM" type="Asia/Ashgabat"/>
<mapZone other="West Asia Standard Time" territory="UZ" type="Asia/Tashkent Asia/Samarkand"/>
<mapZone other="West Asia Standard Time" territory="ZZ" type="Etc/GMT-5"/>
<!-- (UTC+05:00) Ekaterinburg -->
<mapZone other="Ekaterinburg Standard Time" territory="001" type="Asia/Yekaterinburg"/>
<mapZone other="Ekaterinburg Standard Time" territory="RU" type="Asia/Yekaterinburg"/>
<!-- (UTC+05:00) Islamabad, Karachi -->
<mapZone other="Pakistan Standard Time" territory="001" type="Asia/Karachi"/>
<mapZone other="Pakistan Standard Time" territory="PK" type="Asia/Karachi"/>
<!-- (UTC+05:30) Chennai, Kolkata, Mumbai, New Delhi -->
<mapZone other="India Standard Time" territory="001" type="Asia/Calcutta"/>
<mapZone other="India Standard Time" territory="IN" type="Asia/Calcutta"/>
<!-- (UTC+05:30) Sri Jayawardenepura -->
<mapZone other="Sri Lanka Standard Time" territory="001" type="Asia/Colombo"/>
<mapZone other="Sri Lanka Standard Time" territory="LK" type="Asia/Colombo"/>
<!-- (UTC+05:45) Kathmandu -->
<mapZone other="Nepal Standard Time" territory="001" type="Asia/Katmandu"/>
<mapZone other="Nepal Standard Time" territory="NP" type="Asia/Katmandu"/>
<!-- (UTC+06:00) Astana -->
<mapZone other="Central Asia Standard Time" territory="001" type="Asia/Almaty"/>
<mapZone other="Central Asia Standard Time" territory="AQ" type="Antarctica/Vostok"/>
<mapZone other="Central Asia Standard Time" territory="CN" type="Asia/Urumqi"/>
<mapZone other="Central Asia Standard Time" territory="IO" type="Indian/Chagos"/>
<mapZone other="Central Asia Standard Time" territory="KG" type="Asia/Bishkek"/>
<mapZone other="Central Asia Standard Time" territory="KZ" type="Asia/Almaty Asia/Qyzylorda"/>
<mapZone other="Central Asia Standard Time" territory="ZZ" type="Etc/GMT-6"/>
<!-- (UTC+06:00) Dhaka -->
<mapZone other="Bangladesh Standard Time" territory="001" type="Asia/Dhaka"/>
<mapZone other="Bangladesh Standard Time" territory="BD" type="Asia/Dhaka"/>
<mapZone other="Bangladesh Standard Time" territory="BT" type="Asia/Thimphu"/>
<!-- (UTC+06:00) Omsk -->
<mapZone other="Omsk Standard Time" territory="001" type="Asia/Omsk"/>
<mapZone other="Omsk Standard Time" territory="RU" type="Asia/Omsk"/>
<!-- (UTC+06:30) Yangon (Rangoon) -->
<mapZone other="Myanmar Standard Time" territory="001" type="Asia/Rangoon"/>
<mapZone other="Myanmar Standard Time" territory="CC" type="Indian/Cocos"/>
<mapZone other="Myanmar Standard Time" territory="MM" type="Asia/Rangoon"/>
<!-- (UTC+07:00) Bangkok, Hanoi, Jakarta -->
<mapZone other="SE Asia Standard Time" territory="001" type="Asia/Bangkok"/>
<mapZone other="SE Asia Standard Time" territory="AQ" type="Antarctica/Davis"/>
<mapZone other="SE Asia Standard Time" territory="CX" type="Indian/Christmas"/>
<mapZone other="SE Asia Standard Time" territory="ID" type="Asia/Jakarta Asia/Pontianak"/>
<mapZone other="SE Asia Standard Time" territory="KH" type="Asia/Phnom_Penh"/>
<mapZone other="SE Asia Standard Time" territory="LA" type="Asia/Vientiane"/>
<mapZone other="SE Asia Standard Time" territory="TH" type="Asia/Bangkok"/>
<mapZone other="SE Asia Standard Time" territory="VN" type="Asia/Saigon"/>
<mapZone other="SE Asia Standard Time" territory="ZZ" type="Etc/GMT-7"/>
<!-- (UTC+07:00) Barnaul, Gorno-Altaysk -->
<mapZone other="Altai Standard Time" territory="001" type="Asia/Barnaul"/>
<mapZone other="Altai Standard Time" territory="RU" type="Asia/Barnaul"/>
<!-- (UTC+07:00) Hovd -->
<mapZone other="W. Mongolia Standard Time" territory="001" type="Asia/Hovd"/>
<mapZone other="W. Mongolia Standard Time" territory="MN" type="Asia/Hovd"/>
<!-- (UTC+07:00) Krasnoyarsk -->
<mapZone other="North Asia Standard Time" territory="001" type="Asia/Krasnoyarsk"/>
<mapZone other="North Asia Standard Time" territory="RU" type="Asia/Krasnoyarsk Asia/Novokuznetsk"/>
<!-- (UTC+07:00) Novosibirsk -->
<mapZone other="N. Central Asia Standard Time" territory="001" type="Asia/Novosibirsk"/>
<mapZone other="N. Central Asia Standard Time" territory="RU" type="Asia/Novosibirsk"/>
<!-- (UTC+07:00) Tomsk -->
<mapZone other="Tomsk Standard Time" territory="001" type="Asia/Tomsk"/>
<mapZone other="Tomsk Standard Time" territory="RU" type="Asia/Tomsk"/>
<!-- (UTC+08:00) Beijing, Chongqing, Hong Kong, Urumqi -->
<mapZone other="China Standard Time" territory="001" type="Asia/Shanghai"/>
<mapZone other="China Standard Time" territory="CN" type="Asia/Shanghai"/>
<mapZone other="China Standard Time" territory="HK" type="Asia/Hong_Kong"/>
<mapZone other="China Standard Time" territory="MO" type="Asia/Macau"/>
<!-- (UTC+08:00) Irkutsk -->
<mapZone other="North Asia East Standard Time" territory="001" type="Asia/Irkutsk"/>
<mapZone other="North Asia East Standard Time" territory="RU" type="Asia/Irkutsk"/>
<!-- (UTC+08:00) Kuala Lumpur, Singapore -->
<mapZone other="Singapore Standard Time" territory="001" type="Asia/Singapore"/>
<mapZone other="Singapore Standard Time" territory="BN" type="Asia/Brunei"/>
<mapZone other="Singapore Standard Time" territory="ID" type="Asia/Makassar"/>
<mapZone other="Singapore Standard Time" territory="MY" type="Asia/Kuala_Lumpur Asia/Kuching"/>
<mapZone other="Singapore Standard Time" territory="PH" type="Asia/Manila"/>
<mapZone other="Singapore Standard Time" territory="SG" type="Asia/Singapore"/>
<mapZone other="Singapore Standard Time" territory="ZZ" type="Etc/GMT-8"/>
<!-- (UTC+08:00) Perth -->
<mapZone other="W. Australia Standard Time" territory="001" type="Australia/Perth"/>
<mapZone other="W. Australia Standard Time" territory="AU" type="Australia/Perth"/>
<!-- (UTC+08:00) Taipei -->
<mapZone other="Taipei Standard Time" territory="001" type="Asia/Taipei"/>
<mapZone other="Taipei Standard Time" territory="TW" type="Asia/Taipei"/>
<!-- (UTC+08:00) Ulaanbaatar -->
<mapZone other="Ulaanbaatar Standard Time" territory="001" type="Asia/Ulaanbaatar"/>
<mapZone other="Ulaanbaatar Standard Time" territory="MN" type="Asia/Ulaanbaatar Asia/Choibalsan"/>
<!-- (UTC+08:30) Pyongyang -->
<mapZone other="North Korea Standard Time" territory="001" type="Asia/Pyongyang"/>
<mapZone other="North Korea Standard Time" territory="KP" type="Asia/Pyongyang"/>
<!-- (UTC+08:45) Eucla -->
<mapZone other="Aus Central W. Standard Time" territory="001" type="Australia/Eucla"/>
<mapZone other="Aus Central W. Standard Time" territory="AU" type="Australia/Eucla"/>
<!-- (UTC+09:00) Chita -->
<mapZone other="Transbaikal Standard Time" territory="001" type="Asia/Chita"/>
<mapZone other="Transbaikal Standard Time" territory="RU" type="Asia/Chita"/>
<!-- (UTC+09:00) Osaka, Sapporo, Tokyo -->
<mapZone other="Tokyo Standard Time" territory="001" type="Asia/Tokyo"/>
<mapZone other="Tokyo Standard Time" territory="ID" type="Asia/Jayapura"/>
<mapZone other="Tokyo Standard Time" territory="JP" type="Asia/Tokyo"/>
<mapZone other="Tokyo Standard Time" territory="PW" type="Pacific/Palau"/>
<mapZone other="Tokyo Standard Time" territory="TL" type="Asia/Dili"/>
<mapZone other="Tokyo Standard Time" territory="ZZ" type="Etc/GMT-9"/>
<!-- (UTC+09:00) Seoul -->
<mapZone other="Korea Standard Time" territory="001" type="Asia/Seoul"/>
<mapZone other="Korea Standard Time" territory="KR" type="Asia/Seoul"/>
<!-- (UTC+09:00) Yakutsk -->
<mapZone other="Yakutsk Standard Time" territory="001" type="Asia/Yakutsk"/>
<mapZone other="Yakutsk Standard Time" territory="RU" type="Asia/Yakutsk Asia/Khandyga"/>
<!-- (UTC+09:30) Adelaide -->
<mapZone other="Cen. Australia Standard Time" territory="001" type="Australia/Adelaide"/>
<mapZone other="Cen. Australia Standard Time" territory="AU" type="Australia/Adelaide Australia/Broken_Hill"/>
<!-- (UTC+09:30) Darwin -->
<mapZone other="AUS Central Standard Time" territory="001" type="Australia/Darwin"/>
<mapZone other="AUS Central Standard Time" territory="AU" type="Australia/Darwin"/>
<!-- (UTC+10:00) Brisbane -->
<mapZone other="E. Australia Standard Time" territory="001" type="Australia/Brisbane"/>
<mapZone other="E. Australia Standard Time" territory="AU" type="Australia/Brisbane Australia/Lindeman"/>
<!-- (UTC+10:00) Canberra, Melbourne, Sydney -->
<mapZone other="AUS Eastern Standard Time" territory="001" type="Australia/Sydney"/>
<mapZone other="AUS Eastern Standard Time" territory="AU" type="Australia/Sydney Australia/Melbourne"/>
<!-- (UTC+10:00) Guam, Port Moresby -->
<mapZone other="West Pacific Standard Time" territory="001" type="Pacific/Port_Moresby"/>
<mapZone other="West Pacific Standard Time" territory="AQ" type="Antarctica/DumontDUrville"/>
<mapZone other="West Pacific Standard Time" territory="FM" type="Pacific/Truk"/>
<mapZone other="West Pacific Standard Time" territory="GU" type="Pacific/Guam"/>
<mapZone other="West Pacific Standard Time" territory="MP" type="Pacific/Saipan"/>
<mapZone other="West Pacific Standard Time" territory="PG" type="Pacific/Port_Moresby"/>
<mapZone other="West Pacific Standard Time" territory="ZZ" type="Etc/GMT-10"/>
<!-- (UTC+10:00) Hobart -->
<mapZone other="Tasmania Standard Time" territory="001" type="Australia/Hobart"/>
<mapZone other="Tasmania Standard Time" territory="AU" type="Australia/Hobart Australia/Currie"/>
<!-- (UTC+10:00) Vladivostok -->
<mapZone other="Vladivostok Standard Time" territory="001" type="Asia/Vladivostok"/>
<mapZone other="Vladivostok Standard Time" territory="RU" type="Asia/Vladivostok Asia/Ust-Nera"/>
<!-- (UTC+10:30) Lord Howe Island -->
<mapZone other="Lord Howe Standard Time" territory="001" type="Australia/Lord_Howe"/>
<mapZone other="Lord Howe Standard Time" territory="AU" type="Australia/Lord_Howe"/>
<!-- (UTC+11:00) Bougainville Island -->
<mapZone other="Bougainville Standard Time" territory="001" type="Pacific/Bougainville"/>
<mapZone other="Bougainville Standard Time" territory="PG" type="Pacific/Bougainville"/>
<!-- (UTC+11:00) Chokurdakh -->
<mapZone other="Russia Time Zone 10" territory="001" type="Asia/Srednekolymsk"/>
<mapZone other="Russia Time Zone 10" territory="RU" type="Asia/Srednekolymsk"/>
<!-- (UTC+11:00) Magadan -->
<mapZone other="Magadan Standard Time" territory="001" type="Asia/Magadan"/>
<mapZone other="Magadan Standard Time" territory="RU" type="Asia/Magadan"/>
<!-- (UTC+11:00) Norfolk Island -->
<mapZone other="Norfolk Standard Time" territory="001" type="Pacific/Norfolk"/>
<mapZone other="Norfolk Standard Time" territory="NF" type="Pacific/Norfolk"/>
<!-- (UTC+11:00) Sakhalin -->
<mapZone other="Sakhalin Standard Time" territory="001" type="Asia/Sakhalin"/>
<mapZone other="Sakhalin Standard Time" territory="RU" type="Asia/Sakhalin"/>
<!-- (UTC+11:00) Solomon Is., New Caledonia -->
<mapZone other="Central Pacific Standard Time" territory="001" type="Pacific/Guadalcanal"/>
<mapZone other="Central Pacific Standard Time" territory="AQ" type="Antarctica/Casey"/>
<mapZone other="Central Pacific Standard Time" territory="AU" type="Antarctica/Macquarie"/>
<mapZone other="Central Pacific Standard Time" territory="FM" type="Pacific/Ponape Pacific/Kosrae"/>
<mapZone other="Central Pacific Standard Time" territory="NC" type="Pacific/Noumea"/>
<mapZone other="Central Pacific Standard Time" territory="SB" type="Pacific/Guadalcanal"/>
<mapZone other="Central Pacific Standard Time" territory="VU" type="Pacific/Efate"/>
<mapZone other="Central Pacific Standard Time" territory="ZZ" type="Etc/GMT-11"/>
<!-- (UTC+12:00) Anadyr, Petropavlovsk-Kamchatsky -->
<mapZone other="Russia Time Zone 11" territory="001" type="Asia/Kamchatka"/>
<mapZone other="Russia Time Zone 11" territory="RU" type="Asia/Kamchatka Asia/Anadyr"/>
<!-- (UTC+12:00) Auckland, Wellington -->
<mapZone other="New Zealand Standard Time" territory="001" type="Pacific/Auckland"/>
<mapZone other="New Zealand Standard Time" territory="AQ" type="Antarctica/McMurdo"/>
<mapZone other="New Zealand Standard Time" territory="NZ" type="Pacific/Auckland"/>
<!-- (UTC+12:00) Coordinated Universal Time+12 -->
<mapZone other="UTC+12" territory="001" type="Etc/GMT-12"/>
<mapZone other="UTC+12" territory="KI" type="Pacific/Tarawa"/>
<mapZone other="UTC+12" territory="MH" type="Pacific/Majuro Pacific/Kwajalein"/>
<mapZone other="UTC+12" territory="NR" type="Pacific/Nauru"/>
<mapZone other="UTC+12" territory="TV" type="Pacific/Funafuti"/>
<mapZone other="UTC+12" territory="UM" type="Pacific/Wake"/>
<mapZone other="UTC+12" territory="WF" type="Pacific/Wallis"/>
<mapZone other="UTC+12" territory="ZZ" type="Etc/GMT-12"/>
<!-- (UTC+12:00) Fiji -->
<mapZone other="Fiji Standard Time" territory="001" type="Pacific/Fiji"/>
<mapZone other="Fiji Standard Time" territory="FJ" type="Pacific/Fiji"/>
<!-- (UTC+12:45) Chatham Islands -->
<mapZone other="Chatham Islands Standard Time" territory="001" type="Pacific/Chatham"/>
<mapZone other="Chatham Islands Standard Time" territory="NZ" type="Pacific/Chatham"/>
<!-- (UTC+13:00) Nuku'alofa -->
<mapZone other="Tonga Standard Time" territory="001" type="Pacific/Tongatapu"/>
<mapZone other="Tonga Standard Time" territory="KI" type="Pacific/Enderbury"/>
<mapZone other="Tonga Standard Time" territory="TK" type="Pacific/Fakaofo"/>
<mapZone other="Tonga Standard Time" territory="TO" type="Pacific/Tongatapu"/>
<mapZone other="Tonga Standard Time" territory="ZZ" type="Etc/GMT-13"/>
<!-- (UTC+13:00) Samoa -->
<mapZone other="Samoa Standard Time" territory="001" type="Pacific/Apia"/>
<mapZone other="Samoa Standard Time" territory="WS" type="Pacific/Apia"/>
<!-- (UTC+14:00) Kiritimati Island -->
<mapZone other="Line Islands Standard Time" territory="001" type="Pacific/Kiritimati"/>
<mapZone other="Line Islands Standard Time" territory="KI" type="Pacific/Kiritimati"/>
<mapZone other="Line Islands Standard Time" territory="ZZ" type="Etc/GMT-14"/>
</mapTimezones>
</windowsZones>
</supplementalData>

View File

@@ -1110,6 +1110,8 @@ class PHPMailer {
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);
$result .= $this->HeaderLine('X-Mailer', 'PHPMailer '.$this->Version.' (phpmailer.sourceforge.net)');

File diff suppressed because it is too large Load Diff

View File

@@ -54,6 +54,8 @@ return array(
'javelin-behavior-aphlict-dropdown',
'javelin-behavior-history-install',
'javelin-behavior-phabricator-gesture',
'javelin-behavior-phabricator-active-nav',
'javelin-behavior-phabricator-nav',
'javelin-behavior-phabricator-remarkup-assist',
'phabricator-textareautils',
'phabricator-file-upload',
@@ -86,12 +88,6 @@ return array(
'javelin-behavior-aphlict-status',
'javelin-behavior-user-menu',
'phabricator-favicon',
'javelin-behavior-phui-tab-group',
'javelin-behavior-phui-submenu',
'phuix-button-view',
'javelin-behavior-comment-actions',
'phuix-form-control-view',
'phuix-autocomplete',
),
'core.pkg.css' => array(
'phabricator-core-css',
@@ -162,14 +158,9 @@ return array(
'phabricator-feed-css',
'phabricator-dashboard-css',
'aphront-multi-column-view-css',
'phui-curtain-object-ref-view-css',
'phui-comment-form-css',
'phui-head-thing-view-css',
'conpherence-durable-column-view',
'phui-button-bar-css',
),
'conpherence.pkg.css' => array(
'conpherence-durable-column-view',
'conpherence-menu-css',
'conpherence-color-css',
'conpherence-message-pane-css',
@@ -196,8 +187,7 @@ return array(
'phabricator-content-source-view-css',
'inline-comment-summary-css',
'phui-inline-comment-view-css',
'diff-tree-view-css',
'phui-formation-view-css',
'phabricator-filetree-view-css',
),
'differential.pkg.js' => array(
'phabricator-drag-and-drop-file-upload',
@@ -214,14 +204,6 @@ return array(
'phabricator-diff-inline',
'phabricator-diff-changeset',
'phabricator-diff-changeset-list',
'phabricator-diff-tree-view',
'phabricator-diff-path-view',
'phuix-formation-view',
'phuix-formation-column-view',
'phuix-formation-flank-view',
'javelin-external-editor-link-engine',
),
'diffusion.pkg.css' => array(
'diffusion-icons-css',
@@ -238,9 +220,4 @@ return array(
'javelin-behavior-maniphest-batch-selector',
'javelin-behavior-maniphest-list-editor',
),
'dark-console.pkg.js' => array(
'javelin-behavior-dark-console',
'phabricator-darklog',
'phabricator-darkmessage',
),
);

View File

@@ -16,9 +16,6 @@ foreach (new LiskMigrationIterator($table) as $doc) {
continue;
}
$new_view_policy = $default_view_policy;
$new_edit_policy = $default_edit_policy;
// If this was previously a magical project wiki page (under projects/, but
// not projects/ itself) we need to apply the project policies. Otherwise,
// apply the default policies.
@@ -38,24 +35,26 @@ foreach (new LiskMigrationIterator($table) as $doc) {
->executeOne();
if ($project) {
$view_policy = nonempty($project->getViewPolicy(), $default_view_policy);
$edit_policy = nonempty($project->getEditPolicy(), $default_edit_policy);
$new_view_policy = $view_policy;
$new_edit_policy = $edit_policy;
$project_name = $project->getName();
echo pht(
"Migrating document %d to project policy %s...\n",
$id,
$project_name);
$doc->setViewPolicy($view_policy);
$doc->setEditPolicy($edit_policy);
$doc->save();
continue;
}
}
echo pht('Migrating document %d to new policy...', $id)."\n";
queryfx(
$conn_w,
'UPDATE %R SET viewPolicy = %s, editPolicy = %s
WHERE id = %d',
$table,
$new_view_policy,
$new_edit_policy,
$id);
echo pht('Migrating document %d to default install policy...', $id)."\n";
$doc->setViewPolicy($default_view_policy);
$doc->setEditPolicy($default_edit_policy);
$doc->save();
}
echo pht('Done.')."\n";

View File

@@ -40,8 +40,7 @@ foreach ($lists as $list) {
if (!$username_okay) {
echo pht(
'Failed to migrate mailing list "%s": unable to generate a unique '.
'username for it.',
$name)."\n";
'username for it.')."\n";
continue;
}

View File

@@ -4,8 +4,18 @@
// underlying file data were not written correctly. This restores edges for
// any missing pastes.
// See T13510. The "pastebin" database was later renamed to "paste", which
// broke this migration. The migration was removed in 2020 since it seems
// plausible that zero installs are impacted (only installs that ran code
// from November 2015 and have not upgraded in five years could possibly be
// impacted).
$table = new PhabricatorPaste();
$edge_type = PhabricatorObjectHasFileEdgeType::EDGECONST;
foreach (new LiskMigrationIterator($table) as $paste) {
$paste_phid = $paste->getPHID();
$file_phid = $paste->getFilePHID();
if (!$file_phid) {
continue;
}
id(new PhabricatorEdgeEditor())
->addEdge($paste_phid, $edge_type, $file_phid)
->save();
}

View File

@@ -1,10 +0,0 @@
CREATE TABLE {$NAMESPACE}_user.user_externalaccountidentifier (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
phid VARBINARY(64) NOT NULL,
externalAccountPHID VARBINARY(64) NOT NULL,
providerConfigPHID VARBINARY(64) NOT NULL,
identifierHash BINARY(12) NOT NULL,
identifierRaw LONGTEXT NOT NULL,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE {$COLLATE_TEXT};

View File

@@ -1,40 +0,0 @@
<?php
$account_table = new PhabricatorExternalAccount();
$identifier_table = new PhabricatorExternalAccountIdentifier();
$conn = $account_table->establishConnection('w');
$table_name = $account_table->getTableName();
$iterator = new LiskRawMigrationIterator($conn, $table_name);
foreach ($iterator as $account_row) {
// We don't need to migrate "accountID" values for "password" accounts,
// since these were dummy values in the first place and are no longer
// read or written after D21014. (There would be no harm in writing these
// rows, but it's easy to skip them.)
if ($account_row['accountType'] === 'password') {
continue;
}
$account_id = $account_row['accountID'];
if (!strlen($account_id)) {
continue;
}
queryfx(
$conn,
'INSERT IGNORE INTO %R (
phid, externalAccountPHID, providerConfigPHID,
identifierHash, identifierRaw,
dateCreated, dateModified)
VALUES (%s, %s, %s, %s, %s, %d, %d)',
$identifier_table,
$identifier_table->generatePHID(),
$account_row['phid'],
$account_row['providerConfigPHID'],
PhabricatorHash::digestForIndex($account_id),
$account_id,
$account_row['dateCreated'],
$account_row['dateModified']);
}

View File

@@ -1,21 +0,0 @@
<?php
// See T13493. This table previously had a UNIQUE KEY on "<accountType,
// accountDomain, accountID>", which is obsolete. The application now violates
// this key, so make sure it gets dropped.
// There's no "IF EXISTS" modifier for "ALTER TABLE" so run this as a PHP patch
// instead of an SQL patch.
$table = new PhabricatorExternalAccount();
$conn = $table->establishConnection('w');
try {
queryfx(
$conn,
'ALTER TABLE %R DROP KEY %T',
$table,
'account_details');
} catch (AphrontQueryException $ex) {
// Ignore.
}

View File

@@ -1,9 +0,0 @@
CREATE TABLE {$NAMESPACE}_paste.paste_paste_fdocument (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
objectPHID VARBINARY(64) NOT NULL,
isClosed BOOL NOT NULL,
authorPHID VARBINARY(64),
ownerPHID VARBINARY(64),
epochCreated INT UNSIGNED NOT NULL,
epochModified INT UNSIGNED NOT NULL
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};

View File

@@ -1,8 +0,0 @@
CREATE TABLE {$NAMESPACE}_paste.paste_paste_ffield (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
documentID INT UNSIGNED NOT NULL,
fieldKey VARCHAR(4) NOT NULL COLLATE {$COLLATE_TEXT},
rawCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT},
termCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT},
normalCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};

View File

@@ -1,5 +0,0 @@
CREATE TABLE {$NAMESPACE}_paste.paste_paste_fngrams (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
documentID INT UNSIGNED NOT NULL,
ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT}
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};

View File

@@ -1,7 +0,0 @@
CREATE TABLE {$NAMESPACE}_paste.paste_paste_fngrams_common (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT},
needsCollection BOOL NOT NULL,
UNIQUE KEY `key_ngram` (ngram),
KEY `key_collect` (needsCollection)
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};

View File

@@ -1,9 +0,0 @@
CREATE TABLE {$NAMESPACE}_differential.differential_viewstate (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
viewerPHID VARBINARY(64) NOT NULL,
objectPHID VARBINARY(64) NOT NULL,
viewState LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT},
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
UNIQUE KEY `key_viewer` (viewerPHID, objectPHID)
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};

View File

@@ -1,2 +0,0 @@
ALTER TABLE {$NAMESPACE}_differential.differential_transaction_comment
ADD attributes LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT};

View File

@@ -1,2 +0,0 @@
ALTER TABLE {$NAMESPACE}_audit.audit_transaction_comment
ADD attributes LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT};

View File

@@ -1,2 +0,0 @@
UPDATE {$NAMESPACE}_differential.differential_transaction_comment
SET attributes = '{}' WHERE attributes = '';

View File

@@ -1,2 +0,0 @@
UPDATE {$NAMESPACE}_audit.audit_transaction_comment
SET attributes = '{}' WHERE attributes = '';

View File

@@ -1 +0,0 @@
DROP TABLE {$NAMESPACE}_differential.differential_changeset_parse_cache;

View File

@@ -1,7 +0,0 @@
CREATE TABLE {$NAMESPACE}_differential.differential_changeset_parse_cache (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
cacheIndex BINARY(12) NOT NULL,
cache LONGBLOB NOT NULL,
dateCreated INT UNSIGNED NOT NULL,
UNIQUE KEY `key_cacheIndex` (cacheIndex)
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};

View File

@@ -1 +0,0 @@
DROP TABLE IF EXISTS {$NAMESPACE}_differential.differential_commit;

View File

@@ -1,46 +0,0 @@
#!/usr/bin/env php
<?php
$root = dirname(dirname(dirname(__FILE__)));
require_once $root.'/scripts/init/init-script.php';
$xml = $root.'/externals/cldr/cldr_windows_timezones.xml';
$xml = Filesystem::readFile($xml);
$xml = new SimpleXMLElement($xml);
$result_map = array();
$ignore = array(
'UTC',
'UTC-11',
'UTC-02',
'UTC-08',
'UTC-09',
'UTC+12',
);
$ignore = array_fuse($ignore);
$zones = $xml->windowsZones->mapTimezones->mapZone;
foreach ($zones as $zone) {
$windows_name = (string)$zone['other'];
$target_name = (string)$zone['type'];
// Ignore the offset-based timezones from the CLDR map, since we handle
// these later.
if (isset($ignore[$windows_name])) {
continue;
}
// We've already seen this timezone so we don't need to add it to the map
// again.
if (isset($result_map[$windows_name])) {
continue;
}
$result_map[$windows_name] = $target_name;
}
asort($result_map);
echo id(new PhutilJSON())
->encodeFormatted($result_map);

View File

@@ -1,126 +0,0 @@
{
"Egypt Standard Time": "Africa/Cairo",
"Morocco Standard Time": "Africa/Casablanca",
"South Africa Standard Time": "Africa/Johannesburg",
"W. Central Africa Standard Time": "Africa/Lagos",
"E. Africa Standard Time": "Africa/Nairobi",
"Libya Standard Time": "Africa/Tripoli",
"Namibia Standard Time": "Africa/Windhoek",
"Aleutian Standard Time": "America/Adak",
"Alaskan Standard Time": "America/Anchorage",
"Tocantins Standard Time": "America/Araguaina",
"Paraguay Standard Time": "America/Asuncion",
"Bahia Standard Time": "America/Bahia",
"SA Pacific Standard Time": "America/Bogota",
"Argentina Standard Time": "America/Buenos_Aires",
"Eastern Standard Time (Mexico)": "America/Cancun",
"Venezuela Standard Time": "America/Caracas",
"SA Eastern Standard Time": "America/Cayenne",
"Central Standard Time": "America/Chicago",
"Mountain Standard Time (Mexico)": "America/Chihuahua",
"Central Brazilian Standard Time": "America/Cuiaba",
"Mountain Standard Time": "America/Denver",
"Greenland Standard Time": "America/Godthab",
"Turks And Caicos Standard Time": "America/Grand_Turk",
"Central America Standard Time": "America/Guatemala",
"Atlantic Standard Time": "America/Halifax",
"Cuba Standard Time": "America/Havana",
"US Eastern Standard Time": "America/Indianapolis",
"SA Western Standard Time": "America/La_Paz",
"Pacific Standard Time": "America/Los_Angeles",
"Central Standard Time (Mexico)": "America/Mexico_City",
"Saint Pierre Standard Time": "America/Miquelon",
"Montevideo Standard Time": "America/Montevideo",
"Eastern Standard Time": "America/New_York",
"US Mountain Standard Time": "America/Phoenix",
"Haiti Standard Time": "America/Port-au-Prince",
"Canada Central Standard Time": "America/Regina",
"Pacific SA Standard Time": "America/Santiago",
"E. South America Standard Time": "America/Sao_Paulo",
"Newfoundland Standard Time": "America/St_Johns",
"Pacific Standard Time (Mexico)": "America/Tijuana",
"Central Asia Standard Time": "Asia/Almaty",
"Jordan Standard Time": "Asia/Amman",
"Arabic Standard Time": "Asia/Baghdad",
"Azerbaijan Standard Time": "Asia/Baku",
"SE Asia Standard Time": "Asia/Bangkok",
"Altai Standard Time": "Asia/Barnaul",
"Middle East Standard Time": "Asia/Beirut",
"India Standard Time": "Asia/Calcutta",
"Transbaikal Standard Time": "Asia/Chita",
"Sri Lanka Standard Time": "Asia/Colombo",
"Syria Standard Time": "Asia/Damascus",
"Bangladesh Standard Time": "Asia/Dhaka",
"Arabian Standard Time": "Asia/Dubai",
"West Bank Standard Time": "Asia/Hebron",
"W. Mongolia Standard Time": "Asia/Hovd",
"North Asia East Standard Time": "Asia/Irkutsk",
"Israel Standard Time": "Asia/Jerusalem",
"Afghanistan Standard Time": "Asia/Kabul",
"Russia Time Zone 11": "Asia/Kamchatka",
"Pakistan Standard Time": "Asia/Karachi",
"Nepal Standard Time": "Asia/Katmandu",
"North Asia Standard Time": "Asia/Krasnoyarsk",
"Magadan Standard Time": "Asia/Magadan",
"N. Central Asia Standard Time": "Asia/Novosibirsk",
"Omsk Standard Time": "Asia/Omsk",
"North Korea Standard Time": "Asia/Pyongyang",
"Myanmar Standard Time": "Asia/Rangoon",
"Arab Standard Time": "Asia/Riyadh",
"Sakhalin Standard Time": "Asia/Sakhalin",
"Korea Standard Time": "Asia/Seoul",
"China Standard Time": "Asia/Shanghai",
"Singapore Standard Time": "Asia/Singapore",
"Russia Time Zone 10": "Asia/Srednekolymsk",
"Taipei Standard Time": "Asia/Taipei",
"West Asia Standard Time": "Asia/Tashkent",
"Georgian Standard Time": "Asia/Tbilisi",
"Iran Standard Time": "Asia/Tehran",
"Tokyo Standard Time": "Asia/Tokyo",
"Tomsk Standard Time": "Asia/Tomsk",
"Ulaanbaatar Standard Time": "Asia/Ulaanbaatar",
"Vladivostok Standard Time": "Asia/Vladivostok",
"Yakutsk Standard Time": "Asia/Yakutsk",
"Ekaterinburg Standard Time": "Asia/Yekaterinburg",
"Caucasus Standard Time": "Asia/Yerevan",
"Azores Standard Time": "Atlantic/Azores",
"Cape Verde Standard Time": "Atlantic/Cape_Verde",
"Greenwich Standard Time": "Atlantic/Reykjavik",
"Cen. Australia Standard Time": "Australia/Adelaide",
"E. Australia Standard Time": "Australia/Brisbane",
"AUS Central Standard Time": "Australia/Darwin",
"Aus Central W. Standard Time": "Australia/Eucla",
"Tasmania Standard Time": "Australia/Hobart",
"Lord Howe Standard Time": "Australia/Lord_Howe",
"W. Australia Standard Time": "Australia/Perth",
"AUS Eastern Standard Time": "Australia/Sydney",
"Dateline Standard Time": "Etc/GMT+12",
"Astrakhan Standard Time": "Europe/Astrakhan",
"W. Europe Standard Time": "Europe/Berlin",
"GTB Standard Time": "Europe/Bucharest",
"Central Europe Standard Time": "Europe/Budapest",
"E. Europe Standard Time": "Europe/Chisinau",
"Turkey Standard Time": "Europe/Istanbul",
"Kaliningrad Standard Time": "Europe/Kaliningrad",
"FLE Standard Time": "Europe/Kiev",
"GMT Standard Time": "Europe/London",
"Belarus Standard Time": "Europe/Minsk",
"Russian Standard Time": "Europe/Moscow",
"Romance Standard Time": "Europe/Paris",
"Russia Time Zone 3": "Europe/Samara",
"Central European Standard Time": "Europe/Warsaw",
"Mauritius Standard Time": "Indian/Mauritius",
"Samoa Standard Time": "Pacific/Apia",
"New Zealand Standard Time": "Pacific/Auckland",
"Bougainville Standard Time": "Pacific/Bougainville",
"Chatham Islands Standard Time": "Pacific/Chatham",
"Easter Island Standard Time": "Pacific/Easter",
"Fiji Standard Time": "Pacific/Fiji",
"Central Pacific Standard Time": "Pacific/Guadalcanal",
"Hawaiian Standard Time": "Pacific/Honolulu",
"Line Islands Standard Time": "Pacific/Kiritimati",
"Marquesas Standard Time": "Pacific/Marquesas",
"Norfolk Standard Time": "Pacific/Norfolk",
"West Pacific Standard Time": "Pacific/Port_Moresby",
"Tonga Standard Time": "Pacific/Tongatapu"
}

View File

@@ -1,131 +0,0 @@
#!/usr/bin/env php
<?php
if (function_exists('pcntl_async_signals')) {
pcntl_async_signals(true);
} else {
declare(ticks = 1);
}
require_once dirname(__FILE__).'/../../__init_script__.php';
if (!posix_isatty(STDOUT)) {
$sid = posix_setsid();
if ($sid <= 0) {
throw new Exception(pht('Failed to create new process session!'));
}
}
$args = new PhutilArgumentParser($argv);
$args->setTagline(pht('daemon executor'));
$args->setSynopsis(<<<EOHELP
**exec_daemon.php** [__options__] __daemon__ ...
Run an instance of __daemon__.
EOHELP
);
$args->parse(
array(
array(
'name' => 'trace',
'help' => pht('Enable debug tracing.'),
),
array(
'name' => 'trace-memory',
'help' => pht('Enable debug memory tracing.'),
),
array(
'name' => 'verbose',
'help' => pht('Enable verbose activity logging.'),
),
array(
'name' => 'label',
'short' => 'l',
'param' => 'label',
'help' => pht(
'Optional process label. Makes "%s" nicer, no behavioral effects.',
'ps'),
),
array(
'name' => 'daemon',
'wildcard' => true,
),
));
$trace_memory = $args->getArg('trace-memory');
$trace_mode = $args->getArg('trace') || $trace_memory;
$verbose = $args->getArg('verbose');
if (function_exists('posix_isatty') && posix_isatty(STDIN)) {
fprintf(STDERR, pht('Reading daemon configuration from stdin...')."\n");
}
$config = @file_get_contents('php://stdin');
$config = id(new PhutilJSONParser())->parse($config);
PhutilTypeSpec::checkMap(
$config,
array(
'log' => 'optional string|null',
'argv' => 'optional list<wild>',
'load' => 'optional list<string>',
'down' => 'optional int',
));
$log = idx($config, 'log');
if ($log) {
ini_set('error_log', $log);
PhutilErrorHandler::setErrorListener(array('PhutilDaemon', 'errorListener'));
}
$load = idx($config, 'load', array());
foreach ($load as $library) {
$library = Filesystem::resolvePath($library);
phutil_load_library($library);
}
PhutilErrorHandler::initialize();
$daemon = $args->getArg('daemon');
if (!$daemon) {
throw new PhutilArgumentUsageException(
pht('Specify which class of daemon to start.'));
} else if (count($daemon) > 1) {
throw new PhutilArgumentUsageException(
pht('Specify exactly one daemon to start.'));
} else {
$daemon = head($daemon);
if (!class_exists($daemon)) {
throw new PhutilArgumentUsageException(
pht(
'No class "%s" exists in any known library.',
$daemon));
} else if (!is_subclass_of($daemon, 'PhutilDaemon')) {
throw new PhutilArgumentUsageException(
pht(
'Class "%s" is not a subclass of "%s".',
$daemon,
'PhutilDaemon'));
}
}
$argv = idx($config, 'argv', array());
$daemon = newv($daemon, array($argv));
if ($trace_mode) {
$daemon->setTraceMode();
}
if ($trace_memory) {
$daemon->setTraceMemory();
}
if ($verbose) {
$daemon->setVerbose(true);
}
$down_duration = idx($config, 'down');
if ($down_duration) {
$daemon->setScaledownDuration($down_duration);
}
$daemon->execute();

View File

@@ -34,16 +34,6 @@ function write_ini_file($array, $file) {
file_put_contents($file, implode("\n", $res));
}
function getProjectMembersPHIDs($viewer, $project_phid) {
$project = id(new PhabricatorProjectQuery())
->setViewer($viewer)
->needMembers(true)
->withPHIDs(array($project_phid))
->executeOne();
return $project->getMemberPHIDs();
}
// Get user's heys and put them to the configuration
function handleSingleUserPHID(
$keydir, $viewer, $userPHID, $system_keys, &$used_keys) {
@@ -87,141 +77,24 @@ function handleSingleUserPHID(
return $members;
}
function handleUsersPolicyRule(
$keydir, $viewer, $rule, $system_keys, &$used_keys) {
$members = array();
foreach ($rule['value'] as $userPHID) {
$members = array_merge($members,
handleSingleUserPHID($keydir, $viewer, $userPHID,
$system_keys, $used_keys));
}
return $members;
}
function handleProjectsPolicyRule(
$keydir, $viewer, $rule, $system_keys, &$used_keys) {
$members = array();
foreach ($rule['value'] as $projectPHID) {
$memberPHIDs = getProjectMembersPHIDs($viewer, $projectPHID);
foreach ($memberPHIDs as $userPHID) {
$members = array_merge($members,
handleSingleUserPHID($keydir, $viewer, $userPHID,
$system_keys, $used_keys));
}
}
return $members;
}
function handleProjectsAllPolicyRule(
$keydir, $viewer, $rule, $system_keys, &$used_keys) {
$is_first_project = true;
$allowed_members_phids = array();
foreach ($rule['value'] as $project_phid) {
$memberPHIDs = getProjectMembersPHIDs($viewer, $project_phid);
if ($is_first_project) {
$allowed_members_phids = $memberPHIDs;
$is_first_project = false;
} else {
$allowed_members_phids = array_intersect(
$allowed_members_phids, $memberPHIDs);
}
}
$members = array();
foreach ($allowed_members_phids as $userPHID) {
$members = array_merge($members,
handleSingleUserPHID($keydir, $viewer, $userPHID,
$system_keys, $used_keys));
}
return $members;
}
function handleAdministratorsPolicyRule(
$keydir, $viewer, $rule, $system_keys, &$used_keys) {
$administrators = id(new PhabricatorPeopleQuery())
->setViewer($viewer)
->withIsAdmin(true)
->execute();
$members = array();
foreach ($administrators as $administrator) {
$members = array_merge($members,
handleSingleUserPHID($keydir, $viewer, $administrator->getPHID(),
$system_keys, $used_keys));
}
return $members;
}
function handleLegalpadSingleDocument(
$keydir, $viewer, $document, $system_keys, &$used_keys) {
if ($document->getSignatureType() !=
LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL) {
return array();
}
$members = array();
foreach ($document->getSignatures() as $signature) {
if ($signature->getSignatureType() !=
LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL) {
continue;
}
$members = array_merge($members,
handleSingleUserPHID($keydir, $viewer, $signature->getSignerPHID(),
$system_keys, $used_keys));
}
return $members;
}
function handleLegalpadSignaturePolicyRule(
$keydir, $viewer, $rule, $system_keys, &$used_keys) {
$documents = id(new LegalpadDocumentQuery())
->setViewer($viewer)
->withPHIDs($rule['value'])
->needSignatures(true)
->execute();
$members = array();
foreach ($documents as $document) {
$members = array_merge(
$members,
handleLegalpadSingleDocument(
$keydir, $viewer, $document, $system_keys, $used_keys));
}
return $members;
}
function handleCustomPolicy(
$keydir, $viewer, $policy, $system_keys, &$used_keys) {
$members = array();
$rules = $policy->getRules();
foreach ($rules as $rule) {
// Everyone is denied by default anyway
if ($rule['action'] != 'allow') {
continue;
if ($rule['action'] == 'allow') {
if ($rule['rule'] == 'PhabricatorPolicyRuleUsers') {
foreach ($rule['value'] as $userPHID) {
$members = array_merge($members,
handleSingleUserPHID($keydir, $viewer, $userPHID,
$system_keys, $used_keys));
}
} else {
/* pass */
}
}
$policy_members = array();
$rule_type = $rule['rule'];
if ($rule_type == 'PhabricatorPolicyRuleUsers') {
$policy_members = handleUsersPolicyRule(
$keydir, $viewer, $rule, $system_keys, $used_keys);
} else if ($rule_type == 'PhabricatorProjectsPolicyRule') {
$policy_members = handleProjectsPolicyRule(
$keydir, $viewer, $rule, $system_keys, $used_keys);
} else if ($rule_type == 'PhabricatorProjectsAllPolicyRule') {
$policy_members = handleProjectsAllPolicyRule(
$keydir, $viewer, $rule, $system_keys, $used_keys);
} else if ($rule_type == 'PhabricatorAdministratorsPolicyRule') {
$policy_members = handleAdministratorsPolicyRule(
$keydir, $viewer, $rule, $system_keys, $used_keys);
} else if ($rule_type == 'PhabricatorLegalpadSignaturePolicyRule') {
$policy_members = handleLegalpadSignaturePolicyRule(
$keydir, $viewer, $rule, $system_keys, $used_keys);
}
$members = array_merge($members, $policy_members);
}
return $members;
}

View File

@@ -1,504 +0,0 @@
#!/usr/local/bin/php
<?php
$root = dirname(dirname(dirname(__FILE__)));
require_once $root.'/scripts/__init_script__.php';
////////////////////////////////////////////////////////////////////////////////
// Utilities.
//
// TODO(sergey): Move somewhere else. Or, evenmore ideally, use Phabricator's
// utilities instead.
function escape_name($name) {
return preg_replace('/[^A-Za-z0-9]/', '_', $name);
}
function startswith($string, $prefix) {
return substr($string, 0, strlen($prefix)) == $prefix;
}
function endswith($string, $suffix) {
$suffix_length = strlen($suffix);
return substr($string, strlen($string) - $suffix_length,
$suffix_length) == $suffix;
}
function file_put_contents_if_different($file_name, $content) {
if (file_exists($file_name)) {
$current_content = file_get_contents($file_name);
if ($current_content == $content) {
return;
}
}
file_put_contents($file_name, $content);
}
////////////////////////////////////////////////////////////////////////////////
// Phabricator access list traversal.
class Configuration {
// Phabricator user which is used as a viewer.
public $viewer;
// Directory where public keys are stored.
// Full path.
protected $keys_directory;
// Gitolite configuration file (gitolite.conf).
// Full path.
protected $config_file;
// Indexed by key content, contains configuration user name.
protected $stored_keys;
// Indexed by config user name.
protected $used_keys;
// Indexed by committers variable name, contains list of users which are
// configured by Phabricator to be able to commit to the repository.
protected $committers;
public function __construct($gitolite_root) {
$this->viewer = PhabricatorUser::getOmnipotentUser();
$this->keys_directory = "$gitolite_root/keydir";
$this->config_file = "$gitolite_root/conf/gitolite.conf";
$this->collectSystemPublicKeys();
$this->used_keys = array();
$this->committers = array();
if (!file_exists($this->config_file)) {
die("Not found: $this->config_file\n");
}
}
// Store given key of given user.
//
// Includes both storing public key in the file, and storing mapping between
// user and the key.
public function storeUserPublicKey($user, $key) {
$full_key_content = $this->getPublicKeyContent($key);
if (array_key_exists($full_key_content, $this->stored_keys)) {
return $this->stored_keys[$full_key_content];
}
$config_user_name = $this->getConfigUserName($user, $key);
if (!array_key_exists($config_user_name, $this->used_keys)) {
$this->used_keys[$config_user_name] = true;
file_put_contents_if_different("$this->keys_directory/$config_user_name.pub",
$full_key_content);
}
$this->stored_keys[$full_key_content] = $config_user_name;
return $config_user_name;
}
public function setRepositoryUsers($repository, $config_user_names) {
$uri = $repository->getRemoteURI();
$repository_name = basename($uri, '.git');
$variable_name = '@committers_' . escape_name(strtolower($repository_name));
if (array_key_exists($variable_name, $this->committers)) {
die("Duplicate variable mapping for repository $uri\n");
}
$this->committers[$variable_name] = $config_user_names;
}
public function writeNewConfiguration() {
$current_config = file_get_contents($this->config_file);
$current_config_lines = explode("\n", $current_config);
$new_config = "";
foreach ($current_config_lines as $line) {
if (startswith($line, '@committers_')) {
$parts = explode('=', $line);
$variable_name = trim($parts[0]);
if (array_key_exists($variable_name, $this->committers)) {
$system_committers = $this->getNonPhabtricatorUsers($parts[1]);
$all_committers = array_merge(
$system_committers, $this->committers[$variable_name]);
$unique_committers = array_unique($all_committers);
$committers = implode(' ', $unique_committers);
$line = "$variable_name = $committers";
}
}
$new_config .= $line . "\n";
}
file_put_contents_if_different($this->config_file, trim($new_config) . "\n");
}
protected function getNonPhabtricatorUsers($configuration_value) {
$system_users = array();
$users = explode(' ', $configuration_value);
foreach ($users as $user) {
$user = trim($user);
if (empty($user)) {
continue;
}
if (startswith($user, 'PHAB')) {
continue;
}
$system_users[] = $user;
}
return $system_users;
}
public function finalize() {
$this->removeUnusedPublicKeys();
}
// Get content of a public key to be stored in file.
protected function getPublicKeyContent($key) {
return $key->getKeyType().' '.
$key->getKeyBody().' '.
$key->getKeyComment()."\n";
}
// Get user+key name used by the Gitolite configuration.
protected function getConfigUserName($user, $key) {
$escaped_key_name = escape_name($key->getName());
return 'PHAB_'.$user->getUserName().
'_'.$escaped_key_name.
'_'.$key->getID();
}
// Get keys which are not managed by this Phabricator/Git integration script.
//
// Returns map from key content to the key file name. This is used to avoid
// public key duplication in the case system key is used by phabricator user.
protected function collectSystemPublicKeys() {
$files = scandir($this->keys_directory);
foreach ($files as $file) {
if (startswith($file, "PHAB")) {
continue;
}
if (!endswith($file, '.pub')) {
continue;
}
$key = file_get_contents("$this->keys_directory/$file");
$file_we = basename($file, '.pub');
$this->stored_keys[$key] = $file_we;
$this->used_keys[$file_we] = true;
}
}
protected function removeUnusedPublicKeys() {
$files = scandir($this->keys_directory);
foreach ($files as $file) {
if (!startswith($file, "PHAB")) {
continue;
}
$config_user_name = basename($file, '.pub');
if (!array_key_exists($config_user_name, $this->used_keys)) {
unlink("$this->keys_directory/$file");
}
}
}
};
function getProjectMembersPHIDs($viewer, $project_phid) {
$project = id(new PhabricatorProjectQuery())
->setViewer($viewer)
->needMembers(true)
->withPHIDs(array($project_phid))
->executeOne();
return $project->getMemberPHIDs();
}
// Get user's heys and put them to the configuration
function handleSingleUserPHID($config, $userPHID) {
$user = id(new PhabricatorPeopleQuery())
->setViewer($config->viewer)
->withPHIDs(array($userPHID))
->executeOne();
if (!$user) {
return array();
}
if ($user->getIsDisabled()) {
return array();
}
$keys = id(new PhabricatorAuthSSHKey())->loadAllWhere(
'objectPHID = %s',
$user->getPHID());
$config_user_names = array();
foreach ($keys as $key) {
$config_user_name = $config->storeUserPublicKey($user, $key);
$config_user_names[] = $config_user_name;
}
return $config_user_names;
}
function handleUsersPolicyRule($config, $rule) {
$config_user_names = array();
foreach ($rule['value'] as $userPHID) {
$config_user_names = array_merge($config_user_names,
handleSingleUserPHID($config, $userPHID));
}
return $config_user_names;
}
function handleProjectsPolicyRule($config, $rule) {
$config_user_names = array();
foreach ($rule['value'] as $projectPHID) {
$memberPHIDs = getProjectMembersPHIDs($config->viewer, $projectPHID);
foreach ($memberPHIDs as $userPHID) {
$config_user_names = array_merge($config_user_names,
handleSingleUserPHID($config, $userPHID));
}
}
return $config_user_names;
}
function handleProjectsAllPolicyRule($config, $rule) {
$is_first_project = true;
$allowed_members_phids = array();
foreach ($rule['value'] as $project_phid) {
$memberPHIDs = getProjectMembersPHIDs($config->viewer, $project_phid);
if ($is_first_project) {
$allowed_members_phids = $memberPHIDs;
$is_first_project = false;
} else {
$allowed_members_phids = array_intersect(
$allowed_members_phids, $memberPHIDs);
}
}
$config_user_names = array();
foreach ($allowed_members_phids as $userPHID) {
$config_user_names = array_merge($config_user_names,
handleSingleUserPHID($config, $userPHID));
}
return $config_user_names;
}
function handleAdministratorsPolicyRule($config, $rule) {
$administrators = id(new PhabricatorPeopleQuery())
->setViewer($config->viewer)
->withIsAdmin(true)
->execute();
$config_user_names = array();
foreach ($administrators as $administrator) {
$config_user_names = array_merge($config_user_names,
handleSingleUserPHID($config, $administrator->getPHID()));
}
return $config_user_names;
}
function handleLegalpadSingleDocument($config, $document) {
if ($document->getSignatureType() !=
LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL) {
return array();
}
$config_user_names = array();
foreach ($document->getSignatures() as $signature) {
if ($signature->getSignatureType() !=
LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL) {
continue;
}
$config_user_names = array_merge($config_user_names,
handleSingleUserPHID($config, $signature->getSignerPHID()));
}
return $config_user_names;
}
function handleLegalpadSignaturePolicyRule($config, $rule) {
$documents = id(new LegalpadDocumentQuery())
->setViewer($config->viewer)
->withPHIDs($rule['value'])
->needSignatures(true)
->execute();
$config_user_names = array();
foreach ($documents as $document) {
$config_user_names = array_merge($config_user_names,
handleLegalpadSingleDocument($config, $document));
}
return $config_user_names;
}
function handleCustomPolicy($config, $policy) {
$config_user_names = array();
$rules = $policy->getRules();
foreach ($rules as $rule) {
// Everyone is denied by default anyway
if ($rule['action'] != 'allow') {
continue;
}
$policy_config_user_names = array();
$rule_type = $rule['rule'];
if ($rule_type == 'PhabricatorUsersPolicyRule') {
$policy_config_user_names =
handleUsersPolicyRule($config, $rule);
} else if ($rule_type == 'PhabricatorProjectsPolicyRule') {
$policy_config_user_names =
handleProjectsPolicyRule($config, $rule);
} else if ($rule_type == 'PhabricatorProjectsAllPolicyRule') {
$policy_config_user_names =
handleProjectsAllPolicyRule($config, $rule);
} else if ($rule_type == 'PhabricatorAdministratorsPolicyRule') {
$policy_config_user_names =
handleAdministratorsPolicyRule($config, $rule);
} else if ($rule_type == 'PhabricatorLegalpadSignaturePolicyRule') {
$policy_config_user_names =
handleLegalpadSignaturePolicyRule($config, $rule);
}
$config_user_names = array_merge(
$config_user_names, $policy_config_user_names);
}
return $config_user_names;
}
// Parse repository and put it's members to the config file
function handleSingleRepository($config, $repository) {
$policies = PhabricatorPolicyQuery::loadPolicies(
$config->viewer,
$repository);
$pushable = $policies[DiffusionPushCapability::CAPABILITY];
$type = $pushable->getType();
$config_user_names = array();
if ($type == PhabricatorPolicyType::TYPE_PROJECT) {
$project = id(new PhabricatorProjectQuery())
->setViewer($config->viewer)
->needMembers(true)
->withPHIDs(array($pushable->getPHID()))
->executeOne();
$memberPHIDs = $project->getMemberPHIDs();
foreach ($memberPHIDs as $memberPHID) {
$config_user_names = array_merge($config_user_names,
handleSingleUserPHID($config, $memberPHID));
}
} else if ($type == PhabricatorPolicyType::TYPE_USER) {
$config_user_names = handleSingleUserPHID($config, $pushable->getPHID());
} else if ($type == PhabricatorPolicyType::TYPE_CUSTOM) {
$config_user_names = handleCustomPolicy($config, $pushable);
} else {
/* pass */
}
$config->setRepositoryUsers($repository, $config_user_names);
}
function rebuildConfiguration($gitolite_root) {
$config = new Configuration($gitolite_root);
// Fill in new configuration and keys
$used_keys = array();
$repositories = id(new PhabricatorRepositoryQuery())
->setViewer($config->viewer)
->execute();
foreach ($repositories as $repository_id => $repository) {
$type = $repository->getVersionControlSystem();
if ($type == PhabricatorRepositoryType::REPOSITORY_TYPE_GIT) {
handleSingleRepository($config, $repository);
}
}
$config->writeNewConfiguration();
$config->finalize();
return true;
}
////////////////////////////////////////////////////////////////////////////////
// Repository manipulation functionality.
function getGitCommand($repository) {
$git_dir = realpath("$repository/.git");
$git = "git --git-dir='$git_dir'";
$git .= ' --work-tree='.realpath($repository);
return $git;
}
function runGitCommand($repository, $arguments,
&$output=null, &$return_var=null) {
$git = getGitCommand($repository);
$git .= " $arguments";
exec($git, $output, $return_var);
return $return_var == 0;
}
function runGitSshCommand($repository, $key, $arguments,
&$output=null, &$return_var=null) {
$abs_key = realpath($key);
$git = "GIT_SSH_COMMAND=\"ssh -i $key -o IdentitiesOnly=yes\" ";
$git .= getGitCommand($repository);
$git .= " $arguments";
exec($git, $output, $return_var);
return $return_var == 0;
}
function repositoryPull($repository, $key) {
return runGitSshCommand($repository, $key, 'pull --rebase');
}
function repositoryCommitAll($repository, $author, $message) {
if (!runGitCommand(
$repository, 'ls-files --other --exclude-standard', $untracked_files)) {
return false;
}
if (count($untracked_files)) {
$flat_files = join(' ', $untracked_files);
if (!runGitCommand($repository, "add $flat_files")) {
return false;
}
}
runGitCommand($repository, "update-index -q --refresh", $output);
runGitCommand($repository, "diff-index --name-only HEAD --", $output);
if (count($output)) {
return runGitCommand(
$repository, "commit --author='$author' -a -m '$message'", $output);
}
return true;
}
if (count($argv) != 3) {
print("Usage: {$argv[0]} /path/to/gitolite-admin /path/to/id_rsa.pub\n");
exit(1);
}
$gitolite_root = $argv[1];
$key = $argv[2];
if (!repositoryPull($gitolite_root, $key)) {
print("Failed to pull changes from server.\n");
exit(1);
}
if (!rebuildConfiguration($gitolite_root)) {
exit(1);
}
if (!repositoryCommitAll(
$gitolite_root, 'Rebuild Gitadmin <null@git.blender.org>',
'Update to correspond changes in Phabricator')) {
print("Failed to commit changes.\n");
exit(1);
}
runGitSshCommand($gitolite_root, $key, 'push origin master');
?>

View File

@@ -8,14 +8,10 @@ function init_phabricator_script(array $options) {
ini_set(
'include_path',
$include_path.PATH_SEPARATOR.dirname(__FILE__).'/../../../');
$ok = @include_once 'arcanist/support/init/init-script.php';
if (!$ok) {
echo
'FATAL ERROR: Unable to load the "Arcanist" library. '.
'Put "arcanist/" next to "phabricator/" on disk.';
echo "\n";
@include_once 'libphutil/scripts/__init_script__.php';
if (!@constant('__LIBPHUTIL__')) {
echo "ERROR: Unable to load libphutil. Update your PHP 'include_path' to ".
"include the parent directory of libphutil/.\n";
exit(1);
}

View File

@@ -211,29 +211,21 @@ try {
->setUniqueMethod('getName')
->execute();
$command_list = array_keys($workflows);
$command_list = implode(', ', $command_list);
$error_lines = array();
$error_lines[] = pht('Welcome to Phabricator.');
$error_lines[] = pht(
'You are logged in as %s.',
$user_name);
if (!$original_argv) {
$error_lines[] = pht(
'You have not specified a command to run. This means you are requesting '.
'an interactive shell, but Phabricator does not provide interactive '.
'shells over SSH.');
$error_lines[] = pht(
'(Usually, you should run a command like "git clone" or "hg push" '.
'instead of connecting directly with SSH.)');
$error_lines[] = pht(
'Supported commands are: %s.',
$command_list);
$error_lines = implode("\n\n", $error_lines);
throw new PhutilArgumentUsageException($error_lines);
throw new Exception(
pht(
"Welcome to Phabricator.\n\n".
"You are logged in as %s.\n\n".
"You haven't specified a command to run. This means you're requesting ".
"an interactive shell, but Phabricator does not provide an ".
"interactive shell over SSH.\n\n".
"Usually, you should run a command like `%s` or `%s` ".
"rather than connecting directly with SSH.\n\n".
"Supported commands are: %s.",
$user_name,
'git clone',
'hg push',
implode(', ', array_keys($workflows))));
}
$log_argv = implode(' ', $original_argv);
@@ -255,20 +247,7 @@ try {
$parsed_args = new PhutilArgumentParser($parseable_argv);
if (empty($workflows[$command])) {
$error_lines[] = pht(
'You have specified the command "%s", but that command is not '.
'supported by Phabricator. As received by Phabricator, your entire '.
'argument list was:',
$command);
$error_lines[] = csprintf(' $ ssh ... -- %Ls', $parseable_argv);
$error_lines[] = pht(
'Supported commands are: %s.',
$command_list);
$error_lines = implode("\n\n", $error_lines);
throw new PhutilArgumentUsageException($error_lines);
throw new Exception(pht('Invalid command.'));
}
$workflow = $parsed_args->parseWorkflows($workflows);

View File

@@ -12,7 +12,7 @@ function getSVNRepositoryName($repository) {
// Get user's heys and put them to the configuration
function handleSingleUserPHID(
$viewer, $userPHID, $repository, &$namemap, &$access) {
$viewer, $userPHID, $repository, &$authfile, &$access) {
$user = id(new PhabricatorPeopleQuery())
->setViewer($viewer)
->withPHIDs(array($userPHID))
@@ -26,9 +26,21 @@ function handleSingleUserPHID(
}
$user_name = $user->getUserName();
if (!array_key_exists($user_name, $namemap)) {
$namemap[$user_name] = array('email' => $user->loadPrimaryEmailAddress(),
'name' => $user->getRealName());
if (!array_key_exists($user_name, $authfile)) {
$field_list = PhabricatorCustomField::getObjectFields(
$user,
PhabricatorCustomField::ROLE_STORAGE);
$field_list
->setViewer($user)
->readFieldsFromStorage($user);
$fields = $field_list->getFields();
$htaccess_field = idx($fields, 'std:user:htaccess_password_hash');
if ($htaccess_field) {
$password_hash = $htaccess_field->getValueForStorage();
$authfile[$user_name] = array('hash' => $password_hash,
'email' => $user->loadPrimaryEmailAddress(),
'name' => $user->getRealName());
}
}
$repository_name = getSVNRepositoryName($repository);
@@ -67,141 +79,9 @@ function handleSingleUserPHID(
$access[$branches_pathname]['RW'][] = $user_name;
}
function getProjectMembersPHIDs($viewer, $project_phid) {
$project = id(new PhabricatorProjectQuery())
->setViewer($viewer)
->needMembers(true)
->withPHIDs(array($project_phid))
->executeOne();
return $project->getMemberPHIDs();
}
function handleProjectPHID(
$viewer, $project_phid, $repository, &$namemap, &$access) {
$memberPHIDs = getProjectMembersPHIDs($viewer, $project_phid);
foreach ($memberPHIDs as $memberPHID) {
handleSingleUserPHID(
$viewer, $memberPHID, $repository, $namemap, $access);
}
}
function handleUsersPolicyRule(
$viewer, $rule, $repository, &$namemap, &$access) {
foreach ($rule['value'] as $user_phid) {
handleSingleUserPHID(
$viewer, $user_phid, $repository, $namemap, $access);
}
}
function handleProjectsPolicyRule(
$viewer, $rule, $repository, &$namemap, &$access) {
foreach ($rule['value'] as $project_phid) {
handleProjectPHID(
$viewer, $project_phid, $repository, $namemap, $access);
}
}
function handleProjectsAllPolicyRule(
$viewer, $rule, $repository, &$namemap, &$access) {
$is_first_project = true;
$allowed_members_phids = array();
foreach ($rule['value'] as $project_phid) {
$memberPHIDs = getProjectMembersPHIDs($viewer, $project_phid);
if ($is_first_project) {
$allowed_members_phids = $memberPHIDs;
$is_first_project = false;
} else {
$allowed_members_phids = array_intersect(
$allowed_members_phids, $memberPHIDs);
}
}
foreach ($allowed_members_phids as $user_phid) {
handleSingleUserPHID(
$viewer, $user_phid, $repository, $namemap, $access);
}
}
function handleAdministratorsPolicyRule(
$viewer, $rule, $repository, &$namemap, &$access) {
$administrators = id(new PhabricatorPeopleQuery())
->setViewer($viewer)
->withIsAdmin(true)
->execute();
foreach ($administrators as $administrator) {
handleSingleUserPHID(
$viewer, $administrator->getPHID(), $repository, $namemap, $access);
}
}
function handleLegalpadSingleDocument(
$viewer, $document, $repository, &$namemap, &$access) {
if ($document->getSignatureType() !=
LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL) {
return;
}
foreach ($document->getSignatures() as $signature) {
if ($signature->getSignatureType() !=
LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL) {
continue;
}
handleSingleUserPHID(
$viewer, $signature->getSignerPHID(), $repository, $namemap, $access);
}
}
function handleLegalpadSignaturePolicyRule(
$viewer, $rule, $repository, &$namemap, &$access) {
$documents = id(new LegalpadDocumentQuery())
->setViewer($viewer)
->withPHIDs($rule['value'])
->needSignatures(true)
->execute();
foreach ($documents as $document) {
handleLegalpadSingleDocument(
$viewer, $document, $repository, $namemap, $access);
}
}
function handleCustomPolicyRule(
$viewer, $rule, $repository, &$namemap, &$access) {
if ($rule['action'] != PhabricatorPolicy::ACTION_ALLOW) {
// By default the script decides to DENY unless explicitly allowed.
return;
}
$rule_type = $rule['rule'];
if ($rule_type == 'PhabricatorUsersPolicyRule') {
handleUsersPolicyRule(
$viewer, $rule, $repository, $namemap, $access);
} else if ($rule_type == 'PhabricatorProjectsPolicyRule') {
handleProjectsPolicyRule(
$viewer, $rule, $repository, $namemap, $access);
} else if ($rule_type == 'PhabricatorProjectsAllPolicyRule') {
handleProjectsAllPolicyRule(
$viewer, $rule, $repository, $namemap, $access);
} else if ($rule_type == 'PhabricatorAdministratorsPolicyRule') {
handleAdministratorsPolicyRule(
$viewer, $rule, $repository, $namemap, $access);
} else if ($rule_type == 'PhabricatorLegalpadSignaturePolicyRule') {
handleLegalpadSignaturePolicyRule(
$viewer, $rule, $repository, $namemap, $access);
}
}
function handleCustomPolicy(
$viewer, $policy, $repository, &$namemap, &$access) {
foreach ($policy->getRules() as $rule) {
handleCustomPolicyRule($viewer, $rule, $repository, $namemap, $access);
}
}
// Parse repository and put it's members to the config file
function handleSingleRepository(
$viewer, $repository, &$namemap, &$access) {
$viewer, $repository, &$authfile, &$access) {
$policies = PhabricatorPolicyQuery::loadPolicies(
$viewer,
$repository);
@@ -217,14 +97,21 @@ function handleSingleRepository(
}
if ($type == PhabricatorProjectProjectPHIDType::TYPECONST) {
handleProjectPHID(
$viewer, $pushable->getPHID(), $repository, $namemap, $access);
$project = id(new PhabricatorProjectQuery())
->setViewer($viewer)
->needMembers(true)
->withPHIDs(array($pushable->getPHID()))
->executeOne();
$memberPHIDs = $project->getMemberPHIDs();
foreach ($memberPHIDs as $memberPHID) {
handleSingleUserPHID(
$viewer, $memberPHID, $repository, $authfile, $access);
}
} else if ($type == PhabricatorPolicyType::TYPE_USER) {
handleSingleUserPHID(
$viewer, $pushable->getPHID(), $repository, $namemap, $access);
$viewer, $pushable->getPHID(), $repository, $authfile, $access);
} else if ($type == PhabricatorPolicyPHIDTypePolicy::TYPECONST) {
handleCustomPolicy(
$viewer, $pushable, $repository, $namemap, $access);
/* pass */
} else {
/* pass */
@@ -239,7 +126,7 @@ function rebuildConfiguration($what) {
->setViewer($viewer)
->execute();
$namemap = array();
$authfile = array();
$access = array();
require_once 'archived_repos.php';
@@ -253,11 +140,14 @@ function rebuildConfiguration($what) {
$type = $repository->getVersionControlSystem();
if ($type == PhabricatorRepositoryType::REPOSITORY_TYPE_SVN) {
handleSingleRepository(
$viewer, $repository, $namemap, $access);
$viewer, $repository, $authfile, $access);
}
}
if ($what == 'ACCESS') {
if ($what == 'AUTHFILE') {
foreach ($authfile as $user => $data) {
print("$user:${data['hash']}\n");
}
} else if ($what == 'ACCESS') {
foreach ($access as $repository => $users) {
print("[$repository]\n");
$rw_users = array();
@@ -274,17 +164,16 @@ function rebuildConfiguration($what) {
print("* = r\n\n");
}
} else if ($what == 'NAMEMAP') {
foreach ($namemap as $user => $data) {
foreach ($authfile as $user => $data) {
print("$user\t${data['email']}\t${data['name']}\n");
}
}
return true;
}
if (count($argv) != 2 ||
($argv[1] != 'ACCESS' && $argv[1] != 'NAMEMAP')) {
print("Usage: {$argv[0]} ACCESS|NAMEMAP\n");
($argv[1] != 'ACCESS' && $argv[1] != 'AUTHFILE' && $argv[1] != 'NAMEMAP')) {
print("Usage: {$argv[0]} ACCESS|AUTHFILE|NAMEMAP\n");
exit(1);
}

File diff suppressed because it is too large Load Diff

View File

@@ -30,7 +30,6 @@ final class AphrontRequest extends Phobject {
private $controller;
private $uriData = array();
private $cookiePrefix;
private $submitKey;
public function __construct($host, $path) {
$this->host = $host;
@@ -915,19 +914,5 @@ final class AphrontRequest extends Phobject {
return $future;
}
public function updateEphemeralCookies() {
$submit_cookie = PhabricatorCookies::COOKIE_SUBMIT;
$submit_key = $this->getCookie($submit_cookie);
if (strlen($submit_key)) {
$this->clearCookie($submit_cookie);
$this->submitKey = $submit_key;
}
}
public function getSubmitKey() {
return $this->submitKey;
}
}

View File

@@ -1,85 +0,0 @@
<?php
final class AphrontRoutingMapTestCase
extends PhabricatorTestCase {
public function testRoutingMaps() {
$count = 0;
$sites = AphrontSite::getAllSites();
foreach ($sites as $site) {
$maps = $site->getRoutingMaps();
foreach ($maps as $map) {
foreach ($map->getRoutes() as $rule => $value) {
$this->assertRoutable($site, $map, array(), $rule, $value);
$count++;
}
}
}
if (!$count) {
$this->assertSkipped(
pht('No sites define any routing rules.'));
}
}
private function assertRoutable(
AphrontSite $site,
AphrontRoutingMap $map,
array $path,
$rule,
$value) {
$path[] = $rule;
$site_description = $site->getDescription();
$rule_path = implode(' > ', $path);
$pattern = implode('', $path);
$pattern = '('.$pattern.')';
$ok = @preg_match($pattern, '');
$this->assertTrue(
($ok !== false),
pht(
'Routing rule ("%s", for site "%s") does not compile into a '.
'valid regular expression.',
$rule_path,
$site_description));
if (is_array($value)) {
$this->assertTrue(
(count($value) > 0),
pht(
'Routing rule ("%s", for site "%s") does not have any targets.',
$rule_path,
$site_description));
foreach ($value as $sub_rule => $sub_value) {
$this->assertRoutable($site, $map, $path, $sub_rule, $sub_value);
}
return;
}
if (is_string($value)) {
$this->assertTrue(
class_exists($value),
pht(
'Routing rule ("%s", for site "%s") points at controller ("%s") '.
'which does not exist.',
$rule_path,
$site_description,
$value));
return;
}
$this->assertFailure(
pht(
'Routing rule ("%s", for site "%s") points at unknown value '.
'(of type "%s"), expected a controller class name string.',
$rule_path,
$site_description,
phutil_describe_type($value)));
}
}

View File

@@ -27,8 +27,6 @@ final class AphrontApplicationConfiguration
$request->setApplicationConfiguration($this);
$request->setCookiePrefix($cookie_prefix);
$request->updateEphemeralCookies();
return $request;
}
@@ -773,21 +771,12 @@ final class AphrontApplicationConfiguration
);
}
$raw_input = @file_get_contents('php://input');
if ($raw_input !== false) {
$base64_input = base64_encode($raw_input);
} else {
$base64_input = null;
}
$result = array(
'path' => $path,
'params' => $params,
'user' => idx($_SERVER, 'PHP_AUTH_USER'),
'pass' => idx($_SERVER, 'PHP_AUTH_PW'),
'raw.base64' => $base64_input,
// This just makes sure that the response compresses well, so reasonable
// algorithms should want to gzip or deflate it.
'filler' => str_repeat('Q', 1024 * 16),
@@ -812,22 +801,27 @@ final class AphrontApplicationConfiguration
// if we can. Among other things, this corrects variable names with
// the "." character in them, which PHP normally converts into "_".
// If "enable_post_data_reading" is on, the documentation suggests we
// can not read the body. In practice, we seem to be able to. This may
// need to be resolved at some point, likely by instructing installs
// to disable this option.
// There are two major considerations here: whether the
// `enable_post_data_reading` option is set, and whether the content
// type is "multipart/form-data" or not.
// If `enable_post_data_reading` is off, we're free to read the entire
// raw request body and parse it -- and we must, because $_POST and
// $_FILES are not built for us. If `enable_post_data_reading` is on,
// which is the default, we may not be able to read the body (the
// documentation says we can't, but empirically we can at least some
// of the time).
// If the content type is "multipart/form-data", we need to build both
// $_POST and $_FILES, which is involved. The body itself is also more
// difficult to parse than other requests.
$raw_input = PhabricatorStartup::getRawInput();
$parser = new PhutilQueryStringParser();
if (strlen($raw_input)) {
$content_type = idx($_SERVER, 'CONTENT_TYPE');
$is_multipart = preg_match('@^multipart/form-data@i', $content_type);
if ($is_multipart) {
if ($is_multipart && !ini_get('enable_post_data_reading')) {
$multipart_parser = id(new AphrontMultipartParser())
->setContentType($content_type);

View File

@@ -1,150 +0,0 @@
<?php
final class AphrontHTTPHeaderParser extends Phobject {
private $name;
private $content;
private $pairs;
public function parseRawHeader($raw_header) {
$this->name = null;
$this->content = null;
$parts = explode(':', $raw_header, 2);
$this->name = trim($parts[0]);
if (count($parts) > 1) {
$this->content = trim($parts[1]);
}
$this->pairs = null;
return $this;
}
public function getHeaderName() {
$this->requireParse();
return $this->name;
}
public function getHeaderContent() {
$this->requireParse();
return $this->content;
}
public function getHeaderContentAsPairs() {
$content = $this->getHeaderContent();
$state = 'prekey';
$length = strlen($content);
$pair_name = null;
$pair_value = null;
$pairs = array();
$ii = 0;
while ($ii < $length) {
$c = $content[$ii];
switch ($state) {
case 'prekey';
// We're eating space in front of a key.
if ($c == ' ') {
$ii++;
break;
}
$pair_name = '';
$state = 'key';
break;
case 'key';
// We're parsing a key name until we find "=" or ";".
if ($c == ';') {
$state = 'done';
break;
}
if ($c == '=') {
$ii++;
$state = 'value';
break;
}
$ii++;
$pair_name .= $c;
break;
case 'value':
// We found an "=", so now figure out if the value is quoted
// or not.
if ($c == '"') {
$ii++;
$state = 'quoted';
break;
}
$state = 'unquoted';
break;
case 'quoted':
// We're in a quoted string, parse until we find the closing quote.
if ($c == '"') {
$ii++;
$state = 'done';
break;
}
$ii++;
$pair_value .= $c;
break;
case 'unquoted':
// We're in an unquoted string, parse until we find a space or a
// semicolon.
if ($c == ' ' || $c == ';') {
$state = 'done';
break;
}
$ii++;
$pair_value .= $c;
break;
case 'done':
// We parsed something, so eat any trailing whitespace and semicolons
// and look for a new value.
if ($c == ' ' || $c == ';') {
$ii++;
break;
}
$pairs[] = array(
$pair_name,
$pair_value,
);
$pair_name = null;
$pair_value = null;
$state = 'prekey';
break;
}
}
if ($state == 'quoted') {
throw new Exception(
pht(
'Header has unterminated double quote for key "%s".',
$pair_name));
}
if ($pair_name !== null) {
$pairs[] = array(
$pair_name,
$pair_value,
);
}
return $pairs;
}
private function requireParse() {
if ($this->name === null) {
throw new PhutilInvalidStateException('parseRawHeader');
}
}
}

View File

@@ -1,108 +0,0 @@
<?php
final class AphrontHTTPHeaderParserTestCase extends PhutilTestCase {
public function testHeaderParser() {
$cases = array(
array(
'Key: x; y; z',
'Key',
'x; y; z',
array(
array('x', null),
array('y', null),
array('z', null),
),
),
array(
'Content-Disposition: form-data; name="label"',
'Content-Disposition',
'form-data; name="label"',
array(
array('form-data', null),
array('name', 'label'),
),
),
array(
'Content-Type: multipart/form-data; charset=utf-8',
'Content-Type',
'multipart/form-data; charset=utf-8',
array(
array('multipart/form-data', null),
array('charset', 'utf-8'),
),
),
array(
'Content-Type: application/octet-stream; charset="ut',
'Content-Type',
'application/octet-stream; charset="ut',
false,
),
array(
'Content-Type: multipart/form-data; boundary=ABCDEFG',
'Content-Type',
'multipart/form-data; boundary=ABCDEFG',
array(
array('multipart/form-data', null),
array('boundary', 'ABCDEFG'),
),
),
array(
'Content-Type: multipart/form-data; boundary="ABCDEFG"',
'Content-Type',
'multipart/form-data; boundary="ABCDEFG"',
array(
array('multipart/form-data', null),
array('boundary', 'ABCDEFG'),
),
),
);
foreach ($cases as $case) {
$input = $case[0];
$expect_name = $case[1];
$expect_content = $case[2];
$parser = id(new AphrontHTTPHeaderParser())
->parseRawHeader($input);
$actual_name = $parser->getHeaderName();
$actual_content = $parser->getHeaderContent();
$this->assertEqual(
$expect_name,
$actual_name,
pht('Header name for: %s', $input));
$this->assertEqual(
$expect_content,
$actual_content,
pht('Header content for: %s', $input));
if (isset($case[3])) {
$expect_pairs = $case[3];
$caught = null;
try {
$actual_pairs = $parser->getHeaderContentAsPairs();
} catch (Exception $ex) {
$caught = $ex;
}
if ($expect_pairs === false) {
$this->assertEqual(
true,
($caught instanceof Exception),
pht('Expect exception for header pairs of: %s', $input));
} else {
$this->assertEqual(
$expect_pairs,
$actual_pairs,
pht('Header pairs for: %s', $input));
}
}
}
}
}

View File

@@ -1,249 +0,0 @@
<?php
final class AphrontMultipartParser extends Phobject {
private $contentType;
private $boundary;
private $buffer;
private $body;
private $state;
private $part;
private $parts;
public function setContentType($content_type) {
$this->contentType = $content_type;
return $this;
}
public function getContentType() {
return $this->contentType;
}
public function beginParse() {
$content_type = $this->getContentType();
if ($content_type === null) {
throw new PhutilInvalidStateException('setContentType');
}
if (!preg_match('(^multipart/form-data)', $content_type)) {
throw new Exception(
pht(
'Expected "multipart/form-data" content type when executing a '.
'multipart body read.'));
}
$type_parts = preg_split('(\s*;\s*)', $content_type);
$boundary = null;
foreach ($type_parts as $type_part) {
$matches = null;
if (preg_match('(^boundary=(.*))', $type_part, $matches)) {
$boundary = $matches[1];
break;
}
}
if ($boundary === null) {
throw new Exception(
pht('Received "multipart/form-data" request with no "boundary".'));
}
$this->parts = array();
$this->part = null;
$this->buffer = '';
$this->boundary = $boundary;
// We're looking for a (usually empty) body before the first boundary.
$this->state = 'bodynewline';
}
public function continueParse($bytes) {
$this->buffer .= $bytes;
$continue = true;
while ($continue) {
switch ($this->state) {
case 'endboundary':
// We've just parsed a boundary. Next, we expect either "--" (which
// indicates we've reached the end of the parts) or "\r\n" (which
// indicates we should read the headers for the next part).
if (strlen($this->buffer) < 2) {
// We don't have enough bytes yet, so wait for more.
$continue = false;
break;
}
if (!strncmp($this->buffer, '--', 2)) {
// This is "--" after a boundary, so we're done. We'll read the
// rest of the body (the "epilogue") and discard it.
$this->buffer = substr($this->buffer, 2);
$this->state = 'epilogue';
$this->part = null;
break;
}
if (!strncmp($this->buffer, "\r\n", 2)) {
// This is "\r\n" after a boundary, so we're going to going to
// read the headers for a part.
$this->buffer = substr($this->buffer, 2);
$this->state = 'header';
// Create the object to hold the part we're about to read.
$part = new AphrontMultipartPart();
$this->parts[] = $part;
$this->part = $part;
break;
}
throw new Exception(
pht('Expected "\r\n" or "--" after multipart data boundary.'));
case 'header':
// We've just parsed a boundary, followed by "\r\n". We are going
// to read the headers for this part. They are in the form of HTTP
// headers and terminated by "\r\n". The section is terminated by
// a line with no header on it.
if (strlen($this->buffer) < 2) {
// We don't have enough data to find a "\r\n", so wait for more.
$continue = false;
break;
}
if (!strncmp("\r\n", $this->buffer, 2)) {
// This line immediately began "\r\n", so we're done with parsing
// headers. Start parsing the body.
$this->buffer = substr($this->buffer, 2);
$this->state = 'body';
break;
}
// This is an actual header, so look for the end of it.
$header_len = strpos($this->buffer, "\r\n");
if ($header_len === false) {
// We don't have a full header yet, so wait for more data.
$continue = false;
break;
}
$header_buf = substr($this->buffer, 0, $header_len);
$this->part->appendRawHeader($header_buf);
$this->buffer = substr($this->buffer, $header_len + 2);
break;
case 'body':
// We've parsed a boundary and headers, and are parsing the data for
// this part. The data is terminated by "\r\n--", then the boundary.
// We'll look for "\r\n", then switch to the "bodynewline" state if
// we find it.
$marker = "\r";
$marker_pos = strpos($this->buffer, $marker);
if ($marker_pos === false) {
// There's no "\r" anywhere in the buffer, so we can just read it
// as provided. Then, since we read all the data, we're done until
// we get more.
// Note that if we're in the preamble, we won't have a "part"
// object and will just discard the data.
if ($this->part) {
$this->part->appendData($this->buffer);
}
$this->buffer = '';
$continue = false;
break;
}
if ($marker_pos > 0) {
// If there are bytes before the "\r",
if ($this->part) {
$this->part->appendData(substr($this->buffer, 0, $marker_pos));
}
$this->buffer = substr($this->buffer, $marker_pos);
}
$expect = "\r\n";
$expect_len = strlen($expect);
if (strlen($this->buffer) < $expect_len) {
// We don't have enough bytes yet to know if this is "\r\n"
// or not.
$continue = false;
break;
}
if (strncmp($this->buffer, $expect, $expect_len)) {
// The next two bytes aren't "\r\n", so eat them and go looking
// for more newlines.
if ($this->part) {
$this->part->appendData(substr($this->buffer, 0, $expect_len));
}
$this->buffer = substr($this->buffer, $expect_len);
break;
}
// Eat the "\r\n".
$this->buffer = substr($this->buffer, $expect_len);
$this->state = 'bodynewline';
break;
case 'bodynewline':
// We've parsed a newline in a body, or we just started parsing the
// request. In either case, we're looking for "--", then the boundary.
// If we find it, this section is done. If we don't, we consume the
// bytes and move on.
$expect = '--'.$this->boundary;
$expect_len = strlen($expect);
if (strlen($this->buffer) < $expect_len) {
// We don't have enough bytes yet, so wait for more.
$continue = false;
break;
}
if (strncmp($this->buffer, $expect, $expect_len)) {
// This wasn't the boundary, so return to the "body" state and
// consume it. (But first, we need to append the "\r\n" which we
// ate earlier.)
if ($this->part) {
$this->part->appendData("\r\n");
}
$this->state = 'body';
break;
}
// This is the boundary, so toss it and move on.
$this->buffer = substr($this->buffer, $expect_len);
$this->state = 'endboundary';
break;
case 'epilogue':
// We just discard any epilogue.
$this->buffer = '';
$continue = false;
break;
default:
throw new Exception(
pht(
'Unknown parser state "%s".\n',
$this->state));
}
}
}
public function endParse() {
if ($this->state !== 'epilogue') {
throw new Exception(
pht(
'Expected "multipart/form-data" parse to end '.
'in state "epilogue".'));
}
return $this->parts;
}
}

View File

@@ -1,96 +0,0 @@
<?php
final class AphrontMultipartPart extends Phobject {
private $headers = array();
private $value = '';
private $name;
private $filename;
private $tempFile;
private $byteSize = 0;
public function appendRawHeader($bytes) {
$parser = id(new AphrontHTTPHeaderParser())
->parseRawHeader($bytes);
$header_name = $parser->getHeaderName();
$this->headers[] = array(
$header_name,
$parser->getHeaderContent(),
);
if (strtolower($header_name) === 'content-disposition') {
$pairs = $parser->getHeaderContentAsPairs();
foreach ($pairs as $pair) {
list($key, $value) = $pair;
switch ($key) {
case 'filename':
$this->filename = $value;
break;
case 'name':
$this->name = $value;
break;
}
}
}
return $this;
}
public function appendData($bytes) {
$this->byteSize += strlen($bytes);
if ($this->isVariable()) {
$this->value .= $bytes;
} else {
if (!$this->tempFile) {
$this->tempFile = new TempFile(getmypid().'.upload');
}
Filesystem::appendFile($this->tempFile, $bytes);
}
return $this;
}
public function isVariable() {
return ($this->filename === null);
}
public function getName() {
return $this->name;
}
public function getVariableValue() {
if (!$this->isVariable()) {
throw new Exception(pht('This part is not a variable!'));
}
return $this->value;
}
public function getPHPFileDictionary() {
if (!$this->tempFile) {
$this->appendData('');
}
$mime_type = 'application/octet-stream';
foreach ($this->headers as $header) {
list($name, $value) = $header;
if (strtolower($name) == 'content-type') {
$mime_type = $value;
break;
}
}
return array(
'name' => $this->filename,
'type' => $mime_type,
'tmp_name' => (string)$this->tempFile,
'error' => 0,
'size' => $this->byteSize,
);
}
}

View File

@@ -1,45 +0,0 @@
<?php
final class AphrontMultipartParserTestCase extends PhutilTestCase {
public function testParser() {
$map = array(
array(
'data' => 'simple.txt',
'variables' => array(
array('a', 'b'),
),
),
);
$data_dir = dirname(__FILE__).'/data/';
foreach ($map as $test_case) {
$data = Filesystem::readFile($data_dir.$test_case['data']);
$data = str_replace("\n", "\r\n", $data);
$parser = id(new AphrontMultipartParser())
->setContentType('multipart/form-data; boundary=ABCDEFG');
$parser->beginParse();
$parser->continueParse($data);
$parts = $parser->endParse();
$variables = array();
foreach ($parts as $part) {
if (!$part->isVariable()) {
continue;
}
$variables[] = array(
$part->getName(),
$part->getVariableValue(),
);
}
$expect_variables = idx($test_case, 'variables', array());
$this->assertEqual($expect_variables, $variables);
}
}
}

View File

@@ -1,5 +0,0 @@
--ABCDEFG
Content-Disposition: form-data; name="a"
b
--ABCDEFG--

View File

@@ -1,113 +0,0 @@
<?php
final class AphrontRequestStream extends Phobject {
private $encoding;
private $stream;
private $closed;
private $iterator;
public function setEncoding($encoding) {
$this->encoding = $encoding;
return $this;
}
public function getEncoding() {
return $this->encoding;
}
public function getIterator() {
if (!$this->iterator) {
$this->iterator = new PhutilStreamIterator($this->getStream());
}
return $this->iterator;
}
public function readData() {
if (!$this->iterator) {
$iterator = $this->getIterator();
$iterator->rewind();
} else {
$iterator = $this->getIterator();
}
if (!$iterator->valid()) {
return null;
}
$data = $iterator->current();
$iterator->next();
return $data;
}
private function getStream() {
if (!$this->stream) {
$this->stream = $this->newStream();
}
return $this->stream;
}
private function newStream() {
$stream = fopen('php://input', 'rb');
if (!$stream) {
throw new Exception(
pht(
'Failed to open stream "%s" for reading.',
'php://input'));
}
$encoding = $this->getEncoding();
if ($encoding === 'gzip') {
// This parameter is magic. Values 0-15 express a time/memory tradeoff,
// but the largest value (15) corresponds to only 32KB of memory and
// data encoded with a smaller window size than the one we pass can not
// be decompressed. Always pass the maximum window size.
// Additionally, you can add 16 (to enable gzip) or 32 (to enable both
// gzip and zlib). Add 32 to support both.
$zlib_window = 15 + 32;
$ok = stream_filter_append(
$stream,
'zlib.inflate',
STREAM_FILTER_READ,
array(
'window' => $zlib_window,
));
if (!$ok) {
throw new Exception(
pht(
'Failed to append filter "%s" to input stream while processing '.
'a request with "%s" encoding.',
'zlib.inflate',
$encoding));
}
}
return $stream;
}
public static function supportsGzip() {
if (!function_exists('gzencode') || !function_exists('gzdecode')) {
return false;
}
$has_zlib = false;
// NOTE: At least locally, this returns "zlib.*", which is not terribly
// reassuring. We care about "zlib.inflate".
$filters = stream_get_filters();
foreach ($filters as $filter) {
if (!strncasecmp($filter, 'zlib.', strlen('zlib.'))) {
$has_zlib = true;
break;
}
}
return $has_zlib;
}
}

View File

@@ -61,17 +61,11 @@ final class AphrontHTTPProxyResponse extends AphrontResponse {
// Strip "Transfer-Encoding" headers. Particularly, the server we proxied
// may have chunked the response, but cURL will already have un-chunked it.
// If we emit the header and unchunked data, the response becomes invalid.
// See T13517. Strip "Content-Encoding" and "Content-Length" headers, since
// they may reflect compressed content.
foreach ($headers as $key => $header) {
list($header_head, $header_body) = $header;
$header_head = phutil_utf8_strtolower($header_head);
switch ($header_head) {
case 'transfer-encoding':
case 'content-encoding':
case 'content-length':
unset($headers[$key]);
break;
}

View File

@@ -31,10 +31,10 @@ final class AphrontJSONResponse extends AphrontResponse {
}
public function getHeaders() {
$headers = parent::getHeaders();
$headers[] = array('Content-Type', 'application/json');
$headers = array(
array('Content-Type', 'application/json'),
);
$headers = array_merge(parent::getHeaders(), $headers);
return $headers;
}

View File

@@ -10,7 +10,7 @@ abstract class AphrontResponse extends Phobject {
private $contentSecurityPolicyURIs;
private $disableContentSecurityPolicy;
protected $frameable;
private $headers = array();
public function setRequest($request) {
$this->request = $request;
@@ -49,11 +49,6 @@ abstract class AphrontResponse extends Phobject {
return $this;
}
final public function addHeader($key, $value) {
$this->headers[] = array($key, $value);
return $this;
}
/* -( Content )------------------------------------------------------------ */
@@ -110,10 +105,6 @@ abstract class AphrontResponse extends Phobject {
$headers[] = array('Referrer-Policy', 'no-referrer');
foreach ($this->headers as $header) {
$headers[] = $header;
}
return $headers;
}
@@ -426,19 +417,13 @@ abstract class AphrontResponse extends Phobject {
}
public function willBeginWrite() {
// If we've already sent headers, these "ini_set()" calls will warn that
// they have no effect. Today, this always happens because we're inside
// a unit test, so just skip adjusting the setting.
if (!headers_sent()) {
if ($this->shouldCompressResponse()) {
// Enable automatic compression here. Webservers sometimes do this for
// us, but we now detect the absence of compression and warn users about
// it so try to cover our bases more thoroughly.
ini_set('zlib.output_compression', 1);
} else {
ini_set('zlib.output_compression', 0);
}
if ($this->shouldCompressResponse()) {
// Enable automatic compression here. Webservers sometimes do this for
// us, but we now detect the absence of compression and warn users about
// it so try to cover our bases more thoroughly.
ini_set('zlib.output_compression', 1);
} else {
ini_set('zlib.output_compression', 0);
}
}

View File

@@ -1,76 +0,0 @@
<?php
/**
* NOTE: This is very new and unstable.
*/
final class PhutilSprite extends Phobject {
private $sourceFiles = array();
private $sourceX;
private $sourceY;
private $sourceW;
private $sourceH;
private $targetCSS;
private $spriteSheet;
private $name;
public function setName($name) {
$this->name = $name;
return $this;
}
public function getName() {
return $this->name;
}
public function setTargetCSS($target_css) {
$this->targetCSS = $target_css;
return $this;
}
public function getTargetCSS() {
return $this->targetCSS;
}
public function setSourcePosition($x, $y) {
$this->sourceX = $x;
$this->sourceY = $y;
return $this;
}
public function setSourceSize($w, $h) {
$this->sourceW = $w;
$this->sourceH = $h;
return $this;
}
public function getSourceH() {
return $this->sourceH;
}
public function getSourceW() {
return $this->sourceW;
}
public function getSourceY() {
return $this->sourceY;
}
public function getSourceX() {
return $this->sourceX;
}
public function setSourceFile($source_file, $scale = 1) {
$this->sourceFiles[$scale] = $source_file;
return $this;
}
public function getSourceFile($scale) {
if (empty($this->sourceFiles[$scale])) {
throw new Exception(pht("No source file for scale '%s'!", $scale));
}
return $this->sourceFiles[$scale];
}
}

View File

@@ -1,385 +0,0 @@
<?php
/**
* NOTE: This is very new and unstable.
*/
final class PhutilSpriteSheet extends Phobject {
const MANIFEST_VERSION = 1;
const TYPE_STANDARD = 'standard';
const TYPE_REPEAT_X = 'repeat-x';
const TYPE_REPEAT_Y = 'repeat-y';
private $sprites = array();
private $sources = array();
private $hashes = array();
private $cssHeader;
private $generated;
private $scales = array(1);
private $type = self::TYPE_STANDARD;
private $basePath;
private $css;
private $images;
public function addSprite(PhutilSprite $sprite) {
$this->generated = false;
$this->sprites[] = $sprite;
return $this;
}
public function setCSSHeader($header) {
$this->generated = false;
$this->cssHeader = $header;
return $this;
}
public function setScales(array $scales) {
$this->scales = array_values($scales);
return $this;
}
public function getScales() {
return $this->scales;
}
public function setSheetType($type) {
$this->type = $type;
return $this;
}
public function setBasePath($base_path) {
$this->basePath = $base_path;
return $this;
}
private function generate() {
if ($this->generated) {
return;
}
$multi_row = true;
$multi_col = true;
$margin_w = 1;
$margin_h = 1;
$type = $this->type;
switch ($type) {
case self::TYPE_STANDARD:
break;
case self::TYPE_REPEAT_X:
$multi_col = false;
$margin_w = 0;
$width = null;
foreach ($this->sprites as $sprite) {
if ($width === null) {
$width = $sprite->getSourceW();
} else if ($width !== $sprite->getSourceW()) {
throw new Exception(
pht(
"All sprites in a '%s' sheet must have the same width.",
'repeat-x'));
}
}
break;
case self::TYPE_REPEAT_Y:
$multi_row = false;
$margin_h = 0;
$height = null;
foreach ($this->sprites as $sprite) {
if ($height === null) {
$height = $sprite->getSourceH();
} else if ($height !== $sprite->getSourceH()) {
throw new Exception(
pht(
"All sprites in a '%s' sheet must have the same height.",
'repeat-y'));
}
}
break;
default:
throw new Exception(pht("Unknown sprite sheet type '%s'!", $type));
}
$css = array();
if ($this->cssHeader) {
$css[] = $this->cssHeader;
}
$out_w = 0;
$out_h = 0;
// Lay out the sprite sheet. We attempt to build a roughly square sheet
// so it's easier to manage, since 2000x20 is more cumbersome for humans
// to deal with than 200x200.
//
// To do this, we use a simple greedy algorithm, adding sprites one at a
// time. For each sprite, if the sheet is at least as wide as it is tall
// we create a new row. Otherwise, we try to add it to an existing row.
//
// This isn't optimal, but does a reasonable job in most cases and isn't
// too messy.
// Group the sprites by their sizes. We lay them out in the sheet as
// boxes, but then put them into the boxes in the order they were added
// so similar sprites end up nearby on the final sheet.
$boxes = array();
foreach (array_reverse($this->sprites) as $sprite) {
$s_w = $sprite->getSourceW() + $margin_w;
$s_h = $sprite->getSourceH() + $margin_h;
$boxes[$s_w][$s_h][] = $sprite;
}
$rows = array();
foreach ($this->sprites as $sprite) {
$s_w = $sprite->getSourceW() + $margin_w;
$s_h = $sprite->getSourceH() + $margin_h;
// Choose a row for this sprite.
$maybe = array();
foreach ($rows as $key => $row) {
if ($row['h'] < $s_h) {
// We can only add it to a row if the row is at least as tall as the
// sprite.
continue;
}
// We prefer rows which have the same height as the sprite, and then
// rows which aren't yet very wide.
$wasted_v = ($row['h'] - $s_h);
$wasted_h = ($row['w'] / $out_w);
$maybe[$key] = $wasted_v + $wasted_h;
}
$row_key = null;
if ($maybe && $multi_col) {
// If there were any candidate rows, pick the best one.
asort($maybe);
$row_key = head_key($maybe);
}
if ($row_key !== null && $multi_row) {
// If there's a candidate row, but adding the sprite to it would make
// the sprite wider than it is tall, create a new row instead. This
// generally keeps the sprite square-ish.
if ($rows[$row_key]['w'] + $s_w > $out_h) {
$row_key = null;
}
}
if ($row_key === null) {
// Add a new row.
$rows[] = array(
'w' => 0,
'h' => $s_h,
'boxes' => array(),
);
$row_key = last_key($rows);
$out_h += $s_h;
}
// Add the sprite box to the row.
$row = $rows[$row_key];
$row['w'] += $s_w;
$row['boxes'][] = array($s_w, $s_h);
$rows[$row_key] = $row;
$out_w = max($row['w'], $out_w);
}
$images = array();
foreach ($this->scales as $scale) {
$img = imagecreatetruecolor($out_w * $scale, $out_h * $scale);
imagesavealpha($img, true);
imagefill($img, 0, 0, imagecolorallocatealpha($img, 0, 0, 0, 127));
$images[$scale] = $img;
}
// Put the shorter rows first. At the same height, put the wider rows first.
// This makes the resulting sheet more human-readable.
foreach ($rows as $key => $row) {
$rows[$key]['sort'] = $row['h'] + (1 - ($row['w'] / $out_w));
}
$rows = isort($rows, 'sort');
$pos_x = 0;
$pos_y = 0;
$rules = array();
foreach ($rows as $row) {
$max_h = 0;
foreach ($row['boxes'] as $box) {
$sprite = array_pop($boxes[$box[0]][$box[1]]);
foreach ($images as $scale => $img) {
$src = $this->loadSource($sprite, $scale);
imagecopy(
$img,
$src,
$scale * $pos_x, $scale * $pos_y,
$scale * $sprite->getSourceX(), $scale * $sprite->getSourceY(),
$scale * $sprite->getSourceW(), $scale * $sprite->getSourceH());
}
$rule = $sprite->getTargetCSS();
$cssx = (-$pos_x).'px';
$cssy = (-$pos_y).'px';
$rules[$sprite->getName()] = "{$rule} {\n".
" background-position: {$cssx} {$cssy};\n}";
$pos_x += $sprite->getSourceW() + $margin_w;
$max_h = max($max_h, $sprite->getSourceH());
}
$pos_x = 0;
$pos_y += $max_h + $margin_h;
}
// Generate CSS rules in input order.
foreach ($this->sprites as $sprite) {
$css[] = $rules[$sprite->getName()];
}
$this->images = $images;
$this->css = implode("\n\n", $css)."\n";
$this->generated = true;
}
public function generateImage($path, $scale = 1) {
$this->generate();
$this->log(pht("Writing sprite '%s'...", $path));
imagepng($this->images[$scale], $path);
return $this;
}
public function generateCSS($path) {
$this->generate();
$this->log(pht("Writing CSS '%s'...", $path));
$out = $this->css;
$out = str_replace('{X}', imagesx($this->images[1]), $out);
$out = str_replace('{Y}', imagesy($this->images[1]), $out);
Filesystem::writeFile($path, $out);
return $this;
}
public function needsRegeneration(array $manifest) {
return ($this->buildManifest() !== $manifest);
}
private function buildManifest() {
$output = array();
foreach ($this->sprites as $sprite) {
$output[$sprite->getName()] = array(
'name' => $sprite->getName(),
'rule' => $sprite->getTargetCSS(),
'hash' => $this->loadSourceHash($sprite),
);
}
ksort($output);
$data = array(
'version' => self::MANIFEST_VERSION,
'sprites' => $output,
'scales' => $this->scales,
'header' => $this->cssHeader,
'type' => $this->type,
);
return $data;
}
public function generateManifest($path) {
$data = $this->buildManifest();
$json = new PhutilJSON();
$data = $json->encodeFormatted($data);
Filesystem::writeFile($path, $data);
return $this;
}
private function log($message) {
echo $message."\n";
}
private function loadSourceHash(PhutilSprite $sprite) {
$inputs = array();
foreach ($this->scales as $scale) {
$file = $sprite->getSourceFile($scale);
// If two users have a project in different places, like:
//
// /home/alincoln/project
// /home/htaft/project
//
// ...we want to ignore the `/home/alincoln` part when hashing the sheet,
// since the sprites don't change when the project directory moves. If
// the base path is set, build the hashes using paths relative to the
// base path.
$file_key = $file;
if ($this->basePath) {
$file_key = Filesystem::readablePath($file, $this->basePath);
}
if (empty($this->hashes[$file_key])) {
$this->hashes[$file_key] = md5(Filesystem::readFile($file));
}
$inputs[] = $file_key;
$inputs[] = $this->hashes[$file_key];
}
$inputs[] = $sprite->getSourceX();
$inputs[] = $sprite->getSourceY();
$inputs[] = $sprite->getSourceW();
$inputs[] = $sprite->getSourceH();
return md5(implode(':', $inputs));
}
private function loadSource(PhutilSprite $sprite, $scale) {
$file = $sprite->getSourceFile($scale);
if (empty($this->sources[$file])) {
$data = Filesystem::readFile($file);
$image = imagecreatefromstring($data);
$this->sources[$file] = array(
'image' => $image,
'x' => imagesx($image),
'y' => imagesy($image),
);
}
$s_w = $sprite->getSourceW() * $scale;
$i_w = $this->sources[$file]['x'];
if ($s_w > $i_w) {
throw new Exception(
pht(
"Sprite source for '%s' is too small (expected width %d, found %d).",
$file,
$s_w,
$i_w));
}
$s_h = $sprite->getSourceH() * $scale;
$i_h = $this->sources[$file]['y'];
if ($s_h > $i_h) {
throw new Exception(
pht(
"Sprite source for '%s' is too small (expected height %d, found %d).",
$file,
$s_h,
$i_h));
}
return $this->sources[$file]['image'];
}
}

View File

@@ -1,9 +0,0 @@
<?php
final class AphrontScopedUnguardedWriteCapability extends Phobject {
public function __destruct() {
AphrontWriteGuard::endUnguardedWrites();
}
}

View File

@@ -1,267 +0,0 @@
<?php
/**
* Guard writes against CSRF. The Aphront structure takes care of most of this
* for you, you just need to call:
*
* AphrontWriteGuard::willWrite();
*
* ...before executing a write against any new kind of storage engine. MySQL
* databases and the default file storage engines are already covered, but if
* you introduce new types of datastores make sure their writes are guarded. If
* you don't guard writes and make a mistake doing CSRF checks in a controller,
* a CSRF vulnerability can escape undetected.
*
* If you need to execute writes on a page which doesn't have CSRF tokens (for
* example, because you need to do logging), you can temporarily disable the
* write guard by calling:
*
* AphrontWriteGuard::beginUnguardedWrites();
* do_logging_write();
* AphrontWriteGuard::endUnguardedWrites();
*
* This is dangerous, because it disables the backup layer of CSRF protection
* this class provides. You should need this only very, very rarely.
*
* @task protect Protecting Writes
* @task disable Disabling Protection
* @task manage Managing Write Guards
* @task internal Internals
*/
final class AphrontWriteGuard extends Phobject {
private static $instance;
private static $allowUnguardedWrites = false;
private $callback;
private $allowDepth = 0;
/* -( Managing Write Guards )---------------------------------------------- */
/**
* Construct a new write guard for a request. Only one write guard may be
* active at a time. You must explicitly call @{method:dispose} when you are
* done with a write guard:
*
* $guard = new AphrontWriteGuard($callback);
* // ...
* $guard->dispose();
*
* Normally, you do not need to manage guards yourself -- the Aphront stack
* handles it for you.
*
* This class accepts a callback, which will be invoked when a write is
* attempted. The callback should validate the presence of a CSRF token in
* the request, or abort the request (e.g., by throwing an exception) if a
* valid token isn't present.
*
* @param callable CSRF callback.
* @return this
* @task manage
*/
public function __construct($callback) {
if (self::$instance) {
throw new Exception(
pht(
'An %s already exists. Dispose of the previous guard '.
'before creating a new one.',
__CLASS__));
}
if (self::$allowUnguardedWrites) {
throw new Exception(
pht(
'An %s is being created in a context which permits '.
'unguarded writes unconditionally. This is not allowed and '.
'indicates a serious error.',
__CLASS__));
}
$this->callback = $callback;
self::$instance = $this;
}
/**
* Dispose of the active write guard. You must call this method when you are
* done with a write guard. You do not normally need to call this yourself.
*
* @return void
* @task manage
*/
public function dispose() {
if (!self::$instance) {
throw new Exception(pht(
'Attempting to dispose of write guard, but no write guard is active!'));
}
if ($this->allowDepth > 0) {
throw new Exception(
pht(
'Imbalanced %s: more %s calls than %s calls.',
__CLASS__,
'beginUnguardedWrites()',
'endUnguardedWrites()'));
}
self::$instance = null;
}
/**
* Determine if there is an active write guard.
*
* @return bool
* @task manage
*/
public static function isGuardActive() {
return (bool)self::$instance;
}
/**
* Return on instance of AphrontWriteGuard if it's active, or null
*
* @return AphrontWriteGuard|null
*/
public static function getInstance() {
return self::$instance;
}
/* -( Protecting Writes )-------------------------------------------------- */
/**
* Declare intention to perform a write, validating that writes are allowed.
* You should call this method before executing a write whenever you implement
* a new storage engine where information can be permanently kept.
*
* Writes are permitted if:
*
* - The request has valid CSRF tokens.
* - Unguarded writes have been temporarily enabled by a call to
* @{method:beginUnguardedWrites}.
* - All write guarding has been disabled with
* @{method:allowDangerousUnguardedWrites}.
*
* If none of these conditions are true, this method will throw and prevent
* the write.
*
* @return void
* @task protect
*/
public static function willWrite() {
if (!self::$instance) {
if (!self::$allowUnguardedWrites) {
throw new Exception(
pht(
'Unguarded write! There must be an active %s to perform writes.',
__CLASS__));
} else {
// Unguarded writes are being allowed unconditionally.
return;
}
}
$instance = self::$instance;
if ($instance->allowDepth == 0) {
call_user_func($instance->callback);
}
}
/* -( Disabling Write Protection )----------------------------------------- */
/**
* Enter a scope which permits unguarded writes. This works like
* @{method:beginUnguardedWrites} but returns an object which will end
* the unguarded write scope when its __destruct() method is called. This
* is useful to more easily handle exceptions correctly in unguarded write
* blocks:
*
* // Restores the guard even if do_logging() throws.
* function unguarded_scope() {
* $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
* do_logging();
* }
*
* @return AphrontScopedUnguardedWriteCapability Object which ends unguarded
* writes when it leaves scope.
* @task disable
*/
public static function beginScopedUnguardedWrites() {
self::beginUnguardedWrites();
return new AphrontScopedUnguardedWriteCapability();
}
/**
* Begin a block which permits unguarded writes. You should use this very
* sparingly, and only for things like logging where CSRF is not a concern.
*
* You must pair every call to @{method:beginUnguardedWrites} with a call to
* @{method:endUnguardedWrites}:
*
* AphrontWriteGuard::beginUnguardedWrites();
* do_logging();
* AphrontWriteGuard::endUnguardedWrites();
*
* @return void
* @task disable
*/
public static function beginUnguardedWrites() {
if (!self::$instance) {
return;
}
self::$instance->allowDepth++;
}
/**
* Declare that you have finished performing unguarded writes. You must
* call this exactly once for each call to @{method:beginUnguardedWrites}.
*
* @return void
* @task disable
*/
public static function endUnguardedWrites() {
if (!self::$instance) {
return;
}
if (self::$instance->allowDepth <= 0) {
throw new Exception(
pht(
'Imbalanced %s: more %s calls than %s calls.',
__CLASS__,
'endUnguardedWrites()',
'beginUnguardedWrites()'));
}
self::$instance->allowDepth--;
}
/**
* Allow execution of unguarded writes. This is ONLY appropriate for use in
* script contexts or other contexts where you are guaranteed to never be
* vulnerable to CSRF concerns. Calling this method is EXTREMELY DANGEROUS
* if you do not understand the consequences.
*
* If you need to perform unguarded writes on an otherwise guarded workflow
* which is vulnerable to CSRF, use @{method:beginUnguardedWrites}.
*
* @return void
* @task disable
*/
public static function allowDangerousUnguardedWrites($allow) {
if (self::$instance) {
throw new Exception(
pht(
'You can not unconditionally disable %s by calling %s while a write '.
'guard is active. Use %s to temporarily allow unguarded writes.',
__CLASS__,
__FUNCTION__.'()',
'beginUnguardedWrites()'));
}
self::$allowUnguardedWrites = true;
}
}

View File

@@ -10,15 +10,13 @@ final class AlmanacConsoleController extends AlmanacController {
$viewer = $request->getViewer();
$menu = id(new PHUIObjectItemListView())
->setViewer($viewer)
->setBig(true);
->setUser($viewer);
$menu->addItem(
id(new PHUIObjectItemView())
->setHeader(pht('Devices'))
->setHref($this->getApplicationURI('device/'))
->setImageIcon('fa-server')
->setClickable(true)
->addAttribute(
pht(
'Create an inventory of physical and virtual hosts and '.
@@ -29,7 +27,6 @@ final class AlmanacConsoleController extends AlmanacController {
->setHeader(pht('Services'))
->setHref($this->getApplicationURI('service/'))
->setImageIcon('fa-plug')
->setClickable(true)
->addAttribute(
pht(
'Create and update services, and map them to interfaces on '.
@@ -40,7 +37,6 @@ final class AlmanacConsoleController extends AlmanacController {
->setHeader(pht('Networks'))
->setHref($this->getApplicationURI('network/'))
->setImageIcon('fa-globe')
->setClickable(true)
->addAttribute(
pht(
'Manage public and private networks.')));
@@ -50,7 +46,6 @@ final class AlmanacConsoleController extends AlmanacController {
->setHeader(pht('Namespaces'))
->setHref($this->getApplicationURI('namespace/'))
->setImageIcon('fa-asterisk')
->setClickable(true)
->addAttribute(
pht('Control who can create new named services and devices.')));
@@ -62,7 +57,6 @@ final class AlmanacConsoleController extends AlmanacController {
->setHeader(pht('Documentation'))
->setHref($docs_uri)
->setImageIcon('fa-book')
->setClickable(true)
->addAttribute(pht('Browse documentation for Almanac.')));
$crumbs = $this->buildApplicationCrumbs();
@@ -70,20 +64,23 @@ final class AlmanacConsoleController extends AlmanacController {
$crumbs->setBorder(true);
$box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Almanac Console'))
->setBackground(PHUIObjectBoxView::WHITE_CONFIG)
->setObjectList($menu);
$launcher_view = id(new PHUILauncherView())
->appendChild($box);
$header = id(new PHUIHeaderView())
->setHeader(pht('Almanac Console'))
->setHeaderIcon('fa-server');
$view = id(new PHUITwoColumnView())
->setFooter($launcher_view);
->setHeader($header)
->setFooter(array(
$box,
));
return $this->newPage()
->setTitle(pht('Almanac Console'))
->setCrumbs($crumbs)
->appendChild($view);
}
}

View File

@@ -57,6 +57,15 @@ final class AlmanacKeys extends Phobject {
}
public static function getClusterSSHUser() {
// NOTE: When instancing, we currently use the SSH username to figure out
// which instance you are connecting to. We can't use the host name because
// we have no way to tell which host you think you're reaching: the SSH
// protocol does not have a mechanism like a "Host" header.
$username = PhabricatorEnv::getEnvConfig('cluster.instance');
if (strlen($username)) {
return $username;
}
$username = PhabricatorEnv::getEnvConfig('diffusion.ssh-user');
if (strlen($username)) {
return $username;

View File

@@ -550,18 +550,11 @@ abstract class PhabricatorAphlictManagementWorkflow
}
private function getStartCommand(array $server_argv) {
$launch_argv = array();
if ($this->debug) {
$launch_argv[] = '--debug=1';
}
return csprintf(
'%R %Ls -- %s %Ls %Ls',
'%R %Ls -- %s %Ls',
$this->getNodeBinary(),
$this->getNodeArgv(),
$this->getAphlictScriptPath(),
$launch_argv,
$server_argv);
}

View File

@@ -105,14 +105,6 @@ final class PhabricatorAuditEditor
switch ($xaction->getTransactionType()) {
case PhabricatorAuditActionConstants::INLINE:
$comment = $xaction->getComment();
$comment->setAttribute('editing', false);
PhabricatorVersionedDraft::purgeDrafts(
$comment->getPHID(),
$this->getActingAsPHID());
return;
case PhabricatorAuditTransaction::TYPE_COMMIT:
return;
}
@@ -240,22 +232,14 @@ final class PhabricatorAuditEditor
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
$auditors_type = DiffusionCommitAuditorsTransaction::TRANSACTIONTYPE;
$xactions = parent::expandTransaction($object, $xaction);
switch ($xaction->getTransactionType()) {
case PhabricatorAuditTransaction::TYPE_COMMIT:
$phids = $this->getAuditRequestTransactionPHIDsFromCommitMessage(
$request = $this->createAuditRequestTransactionFromCommitMessage(
$object);
if ($phids) {
$xactions[] = $object->getApplicationTransactionTemplate()
->setTransactionType($auditors_type)
->setNewValue(
array(
'+' => array_fuse($phids),
));
$this->addUnmentionablePHIDs($phids);
if ($request) {
$xactions[] = $request;
$this->addUnmentionablePHIDs($request->getNewValue());
}
break;
default:
@@ -284,7 +268,7 @@ final class PhabricatorAuditEditor
return $xactions;
}
private function getAuditRequestTransactionPHIDsFromCommitMessage(
private function createAuditRequestTransactionFromCommitMessage(
PhabricatorRepositoryCommit $commit) {
$actor = $this->getActor();
@@ -313,7 +297,12 @@ final class PhabricatorAuditEditor
return array();
}
return $phids;
return $commit->getApplicationTransactionTemplate()
->setTransactionType(DiffusionCommitAuditorsTransaction::TRANSACTIONTYPE)
->setNewValue(
array(
'+' => array_fuse($phids),
));
}
protected function sortTransactions(array $xactions) {
@@ -416,31 +405,6 @@ final class PhabricatorAuditEditor
$phid_map[] = $reverted_phids;
}
// See T13463. Copy "related task" edges from the associated revision, if
// one exists.
$revision = DiffusionCommitRevisionQuery::loadRevisionForCommit(
$actor,
$object);
if ($revision) {
$task_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
$revision->getPHID(),
DifferentialRevisionHasTaskEdgeType::EDGECONST);
$task_phids = array_fuse($task_phids);
if ($task_phids) {
$related_edge = DiffusionCommitHasTaskEdgeType::EDGECONST;
$result[] = id(new PhabricatorAuditTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
->setMetadataValue('edge:type', $related_edge)
->setNewValue(array('+' => $task_phids));
}
// Mark these objects as unmentionable, since the explicit relationship
// is stronger and any mentions are redundant.
$phid_map[] = $task_phids;
}
$phid_map = array_mergev($phid_map);
$this->addUnmentionablePHIDs($phid_map);

View File

@@ -1,75 +0,0 @@
<?php
final class DiffusionInternalCommitSearchEngine
extends PhabricatorApplicationSearchEngine {
public function getResultTypeDescription() {
return pht('Diffusion Raw Commits');
}
public function getApplicationClassName() {
return 'PhabricatorDiffusionApplication';
}
public function newQuery() {
return new DiffusionCommitQuery();
}
protected function buildQueryFromParameters(array $map) {
$query = $this->newQuery();
if ($map['repositoryPHIDs']) {
$query->withRepositoryPHIDs($map['repositoryPHIDs']);
}
return $query;
}
protected function buildCustomSearchFields() {
return array(
id(new PhabricatorSearchDatasourceField())
->setLabel(pht('Repositories'))
->setKey('repositoryPHIDs')
->setDatasource(new DiffusionRepositoryFunctionDatasource())
->setDescription(pht('Find commits in particular repositories.')),
);
}
protected function getURI($path) {
return null;
}
protected function renderResultList(
array $commits,
PhabricatorSavedQuery $query,
array $handles) {
return null;
}
protected function getObjectWireFieldsForConduit(
$object,
array $field_extensions,
array $extension_data) {
$commit = $object;
$viewer = $this->requireViewer();
$repository = $commit->getRepository();
$identifier = $commit->getCommitIdentifier();
id(new DiffusionRepositoryClusterEngine())
->setViewer($viewer)
->setRepository($repository)
->synchronizeWorkingCopyBeforeRead();
$ref = id(new DiffusionLowLevelCommitQuery())
->setRepository($repository)
->withIdentifier($identifier)
->execute();
return array(
'ref' => $ref->newDictionary(),
);
}
}

View File

@@ -221,9 +221,9 @@ final class PhabricatorCommitSearchEngine
$bucket = $this->getResultBucket($query);
$template = id(new DiffusionCommitGraphView())
$template = id(new PhabricatorAuditListView())
->setViewer($viewer)
->setShowAuditors(true);
->setShowDrafts(true);
$views = array();
if ($bucket) {
@@ -235,31 +235,37 @@ final class PhabricatorCommitSearchEngine
foreach ($groups as $group) {
// Don't show groups in Dashboard Panels
if ($group->getObjects() || !$this->isPanelContext()) {
$item_list = id(clone $template)
->setCommits($group->getObjects())
->newObjectItemListView();
$views[] = $item_list
$views[] = id(clone $template)
->setHeader($group->getName())
->setNoDataString($group->getNoDataString());
->setNoDataString($group->getNoDataString())
->setCommits($group->getObjects());
}
}
} catch (Exception $ex) {
$this->addError($ex->getMessage());
}
}
if (!$views) {
$item_list = id(clone $template)
} else {
$views[] = id(clone $template)
->setCommits($commits)
->newObjectItemListView();
$views[] = $item_list
->setNoDataString(pht('No commits found.'));
}
return id(new PhabricatorApplicationSearchResultView())
->setContent($views);
if (!$views) {
$views[] = id(new PhabricatorAuditListView())
->setViewer($viewer)
->setNoDataString(pht('No commits found.'));
}
if (count($views) == 1) {
$list = head($views)->buildList();
} else {
$list = $views;
}
$result = new PhabricatorApplicationSearchResultView();
$result->setContent($list);
return $result;
}
protected function getNewUserBody() {

View File

@@ -1,16 +1,27 @@
<?php
final class PhabricatorAuditInlineComment
extends PhabricatorInlineComment {
extends Phobject
implements PhabricatorInlineCommentInterface {
protected function newStorageObject() {
return new PhabricatorAuditTransactionComment();
private $proxy;
private $syntheticAuthor;
private $isGhost;
public function __construct() {
$this->proxy = new PhabricatorAuditTransactionComment();
}
public function getControllerURI() {
return urisprintf(
'/diffusion/inline/edit/%s/',
$this->getCommitPHID());
public function __clone() {
$this->proxy = clone $this->proxy;
}
public function getTransactionPHID() {
return $this->proxy->getTransactionPHID();
}
public function getTransactionComment() {
return $this->proxy;
}
public function supportsHiding() {
@@ -25,40 +36,246 @@ final class PhabricatorAuditInlineComment
$content_source = PhabricatorContentSource::newForSource(
PhabricatorOldWorldContentSource::SOURCECONST);
$this->getStorageObject()
$this->proxy
->setViewPolicy('public')
->setEditPolicy($this->getAuthorPHID())
->setContentSource($content_source)
->setCommentVersion(1);
return $this->getStorageObject();
return $this->proxy;
}
public static function loadID($id) {
$inlines = id(new PhabricatorAuditTransactionComment())->loadAllWhere(
'id = %d',
$id);
if (!$inlines) {
return null;
}
return head(self::buildProxies($inlines));
}
public static function loadPHID($phid) {
$inlines = id(new PhabricatorAuditTransactionComment())->loadAllWhere(
'phid = %s',
$phid);
if (!$inlines) {
return null;
}
return head(self::buildProxies($inlines));
}
public static function loadDraftComments(
PhabricatorUser $viewer,
$commit_phid,
$raw = false) {
$inlines = id(new DiffusionDiffInlineCommentQuery())
->setViewer($viewer)
->withAuthorPHIDs(array($viewer->getPHID()))
->withCommitPHIDs(array($commit_phid))
->withHasTransaction(false)
->withHasPath(true)
->withIsDeleted(false)
->needReplyToComments(true)
->execute();
if ($raw) {
return $inlines;
}
return self::buildProxies($inlines);
}
public static function loadPublishedComments(
PhabricatorUser $viewer,
$commit_phid) {
$inlines = id(new DiffusionDiffInlineCommentQuery())
->setViewer($viewer)
->withCommitPHIDs(array($commit_phid))
->withHasTransaction(true)
->withHasPath(true)
->execute();
return self::buildProxies($inlines);
}
public static function loadDraftAndPublishedComments(
PhabricatorUser $viewer,
$commit_phid,
$path_id = null) {
if ($path_id === null) {
$inlines = id(new PhabricatorAuditTransactionComment())->loadAllWhere(
'commitPHID = %s AND (transactionPHID IS NOT NULL OR authorPHID = %s)
AND pathID IS NOT NULL',
$commit_phid,
$viewer->getPHID());
} else {
$inlines = id(new PhabricatorAuditTransactionComment())->loadAllWhere(
'commitPHID = %s AND pathID = %d AND
((authorPHID = %s AND isDeleted = 0) OR transactionPHID IS NOT NULL)',
$commit_phid,
$path_id,
$viewer->getPHID());
}
return self::buildProxies($inlines);
}
private static function buildProxies(array $inlines) {
$results = array();
foreach ($inlines as $key => $inline) {
$results[$key] = self::newFromModernComment(
$inline);
}
return $results;
}
public function setSyntheticAuthor($synthetic_author) {
$this->syntheticAuthor = $synthetic_author;
return $this;
}
public function getSyntheticAuthor() {
return $this->syntheticAuthor;
}
public function openTransaction() {
$this->proxy->openTransaction();
}
public function saveTransaction() {
$this->proxy->saveTransaction();
}
public function save() {
$this->getTransactionCommentForSave()->save();
return $this;
}
public function delete() {
$this->proxy->delete();
return $this;
}
public function getID() {
return $this->proxy->getID();
}
public function getPHID() {
return $this->proxy->getPHID();
}
public static function newFromModernComment(
PhabricatorAuditTransactionComment $comment) {
$obj = new PhabricatorAuditInlineComment();
$obj->setStorageObject($comment);
$obj->proxy = $comment;
return $obj;
}
public function isCompatible(PhabricatorInlineCommentInterface $comment) {
return
($this->getAuthorPHID() === $comment->getAuthorPHID()) &&
($this->getSyntheticAuthor() === $comment->getSyntheticAuthor()) &&
($this->getContent() === $comment->getContent());
}
public function setContent($content) {
$this->proxy->setContent($content);
return $this;
}
public function getContent() {
return $this->proxy->getContent();
}
public function isDraft() {
return !$this->proxy->getTransactionPHID();
}
public function setPathID($id) {
$this->getStorageObject()->setPathID($id);
$this->proxy->setPathID($id);
return $this;
}
public function getPathID() {
return $this->getStorageObject()->getPathID();
return $this->proxy->getPathID();
}
public function setIsNewFile($is_new) {
$this->proxy->setIsNewFile($is_new);
return $this;
}
public function getIsNewFile() {
return $this->proxy->getIsNewFile();
}
public function setLineNumber($number) {
$this->proxy->setLineNumber($number);
return $this;
}
public function getLineNumber() {
return $this->proxy->getLineNumber();
}
public function setLineLength($length) {
$this->proxy->setLineLength($length);
return $this;
}
public function getLineLength() {
return $this->proxy->getLineLength();
}
public function setCache($cache) {
return $this;
}
public function getCache() {
return null;
}
public function setAuthorPHID($phid) {
$this->proxy->setAuthorPHID($phid);
return $this;
}
public function getAuthorPHID() {
return $this->proxy->getAuthorPHID();
}
public function setCommitPHID($commit_phid) {
$this->getStorageObject()->setCommitPHID($commit_phid);
$this->proxy->setCommitPHID($commit_phid);
return $this;
}
public function getCommitPHID() {
return $this->getStorageObject()->getCommitPHID();
return $this->proxy->getCommitPHID();
}
// When setting a comment ID, we also generate a phantom transaction PHID for
// the future transaction.
public function setAuditCommentID($id) {
$this->proxy->setLegacyCommentID($id);
$this->proxy->setTransactionPHID(
PhabricatorPHID::generateNewPHID(
PhabricatorApplicationTransactionTransactionPHIDType::TYPECONST,
PhabricatorRepositoryCommitPHIDType::TYPECONST));
return $this;
}
public function getAuditCommentID() {
return $this->proxy->getLegacyCommentID();
}
public function setChangesetID($id) {
@@ -69,4 +286,82 @@ final class PhabricatorAuditInlineComment
return $this->getPathID();
}
public function setReplyToCommentPHID($phid) {
$this->proxy->setReplyToCommentPHID($phid);
return $this;
}
public function getReplyToCommentPHID() {
return $this->proxy->getReplyToCommentPHID();
}
public function setHasReplies($has_replies) {
$this->proxy->setHasReplies($has_replies);
return $this;
}
public function getHasReplies() {
return $this->proxy->getHasReplies();
}
public function setIsDeleted($is_deleted) {
$this->proxy->setIsDeleted($is_deleted);
return $this;
}
public function getIsDeleted() {
return $this->proxy->getIsDeleted();
}
public function setFixedState($state) {
$this->proxy->setFixedState($state);
return $this;
}
public function getFixedState() {
return $this->proxy->getFixedState();
}
public function setIsGhost($is_ghost) {
$this->isGhost = $is_ghost;
return $this;
}
public function getIsGhost() {
return $this->isGhost;
}
public function getDateModified() {
return $this->proxy->getDateModified();
}
public function getDateCreated() {
return $this->proxy->getDateCreated();
}
/* -( PhabricatorMarkupInterface Implementation )-------------------------- */
public function getMarkupFieldKey($field) {
return 'AI:'.$this->getID();
}
public function newMarkupEngine($field) {
return PhabricatorMarkupEngine::newDifferentialMarkupEngine();
}
public function getMarkupText($field) {
return $this->getContent();
}
public function didMarkupText($field, $output, PhutilMarkupEngine $engine) {
return $output;
}
public function shouldUseMarkupCache($field) {
// Only cache submitted comments.
return ($this->getID() && $this->getAuditCommentID());
}
}

View File

@@ -1,9 +1,7 @@
<?php
final class PhabricatorAuditTransactionComment
extends PhabricatorApplicationTransactionComment
implements
PhabricatorInlineCommentInterface {
extends PhabricatorApplicationTransactionComment {
protected $commitPHID;
protected $pathID;
@@ -14,10 +12,8 @@ final class PhabricatorAuditTransactionComment
protected $hasReplies = 0;
protected $replyToCommentPHID;
protected $legacyCommentID;
protected $attributes = array();
private $replyToComment = self::ATTACHABLE;
private $inlineContext = self::ATTACHABLE;
public function getApplicationTransactionObject() {
return new PhabricatorAuditTransaction();
@@ -58,10 +54,6 @@ final class PhabricatorAuditTransactionComment
),
) + $config[self::CONFIG_KEY_SCHEMA];
$config[self::CONFIG_SERIALIZATION] = array(
'attributes' => self::SERIALIZATION_JSON,
) + idx($config, self::CONFIG_SERIALIZATION, array());
return $config;
}
@@ -75,27 +67,4 @@ final class PhabricatorAuditTransactionComment
return $this->assertAttached($this->replyToComment);
}
public function getAttribute($key, $default = null) {
return idx($this->attributes, $key, $default);
}
public function setAttribute($key, $value) {
$this->attributes[$key] = $value;
return $this;
}
public function newInlineCommentObject() {
return PhabricatorAuditInlineComment::newFromModernComment($this);
}
public function getInlineContext() {
return $this->assertAttached($this->inlineContext);
}
public function attachInlineContext(
PhabricatorInlineCommentContext $context = null) {
$this->inlineContext = $context;
return $this;
}
}

View File

@@ -0,0 +1,179 @@
<?php
final class PhabricatorAuditListView extends AphrontView {
private $commits = array();
private $header;
private $showDrafts;
private $noDataString;
private $highlightedAudits;
public function setNoDataString($no_data_string) {
$this->noDataString = $no_data_string;
return $this;
}
public function getNoDataString() {
return $this->noDataString;
}
public function setHeader($header) {
$this->header = $header;
return $this;
}
public function getHeader() {
return $this->header;
}
public function setShowDrafts($show_drafts) {
$this->showDrafts = $show_drafts;
return $this;
}
public function getShowDrafts() {
return $this->showDrafts;
}
/**
* These commits should have both commit data and audit requests attached.
*/
public function setCommits(array $commits) {
assert_instances_of($commits, 'PhabricatorRepositoryCommit');
$this->commits = mpull($commits, null, 'getPHID');
return $this;
}
public function getCommits() {
return $this->commits;
}
private function getCommitDescription($phid) {
if ($this->commits === null) {
return pht('(Unknown Commit)');
}
$commit = idx($this->commits, $phid);
if (!$commit) {
return pht('(Unknown Commit)');
}
$summary = $commit->getCommitData()->getSummary();
if (strlen($summary)) {
return $summary;
}
// No summary, so either this is still importing or just has an empty
// commit message.
if (!$commit->isImported()) {
return pht('(Importing Commit...)');
} else {
return pht('(Untitled Commit)');
}
}
public function render() {
$list = $this->buildList();
$list->setFlush(true);
return $list->render();
}
public function buildList() {
$viewer = $this->getViewer();
$rowc = array();
$phids = array();
foreach ($this->getCommits() as $commit) {
$phids[] = $commit->getPHID();
foreach ($commit->getAudits() as $audit) {
$phids[] = $audit->getAuditorPHID();
}
$author_phid = $commit->getAuthorPHID();
if ($author_phid) {
$phids[] = $author_phid;
}
}
$handles = $viewer->loadHandles($phids);
$show_drafts = $this->getShowDrafts();
$draft_icon = id(new PHUIIconView())
->setIcon('fa-comment yellow')
->addSigil('has-tooltip')
->setMetadata(
array(
'tip' => pht('Unsubmitted Comments'),
));
$list = new PHUIObjectItemListView();
foreach ($this->commits as $commit) {
$commit_phid = $commit->getPHID();
$commit_handle = $handles[$commit_phid];
$committed = null;
$commit_name = $commit_handle->getName();
$commit_link = $commit_handle->getURI();
$commit_desc = $this->getCommitDescription($commit_phid);
$committed = phabricator_datetime($commit->getEpoch(), $viewer);
$status = $commit->getAuditStatusObject();
$status_text = $status->getName();
$status_color = $status->getColor();
$status_icon = $status->getIcon();
$author_phid = $commit->getAuthorPHID();
if ($author_phid) {
$author_name = $handles[$author_phid]->renderLink();
} else {
$author_name = $commit->getCommitData()->getAuthorName();
}
$item = id(new PHUIObjectItemView())
->setObjectName($commit_name)
->setHeader($commit_desc)
->setHref($commit_link)
->setDisabled($commit->isUnreachable())
->addByline(pht('Author: %s', $author_name))
->addIcon('none', $committed);
if ($show_drafts) {
if ($commit->getHasDraft($viewer)) {
$item->addAttribute($draft_icon);
}
}
$audits = $commit->getAudits();
$auditor_phids = mpull($audits, 'getAuditorPHID');
if ($auditor_phids) {
$auditor_list = $handles->newSublist($auditor_phids)
->renderList()
->setAsInline(true);
} else {
$auditor_list = phutil_tag('em', array(), pht('None'));
}
$item->addAttribute(pht('Auditors: %s', $auditor_list));
if ($status_color) {
$item->setStatusIcon($status_icon.' '.$status_color, $status_text);
}
$list->addItem($item);
}
if ($this->noDataString) {
$list->setNoDataString($this->noDataString);
}
if ($this->header) {
$list->setHeader($this->header);
}
return $list;
}
}

View File

@@ -2,7 +2,7 @@
/**
* Abstract interface to an identity provider or authentication source, like
* Twitter, Facebook, or Google.
* Twitter, Facebook or Google.
*
* Generally, adapters are handed some set of credentials particular to the
* provider they adapt, and they turn those credentials into standard
@@ -17,37 +17,13 @@
*/
abstract class PhutilAuthAdapter extends Phobject {
final public function getAccountIdentifiers() {
$result = $this->newAccountIdentifiers();
assert_instances_of($result, 'PhabricatorExternalAccountIdentifier');
return $result;
}
protected function newAccountIdentifiers() {
$identifiers = array();
$raw_identifier = $this->getAccountID();
if ($raw_identifier !== null) {
$identifiers[] = $this->newAccountIdentifier($raw_identifier);
}
return $identifiers;
}
final protected function newAccountIdentifier($raw_identifier) {
return id(new PhabricatorExternalAccountIdentifier())
->setIdentifierRaw($raw_identifier);
}
/**
* Get a unique identifier associated with the account.
* Get a unique identifier associated with the identity. For most providers,
* this is an account ID.
*
* This identifier should be permanent, immutable, and uniquely identify
* the account. If possible, it should be nonsensitive. For providers that
* have a GUID or PHID value for accounts, these are the best values to use.
*
* You can implement @{method:newAccountIdentifiers} instead if a provider
* is unable to emit identifiers with all of these properties.
* The account ID needs to be unique within this adapter's configuration, such
* that `<adapterKey, accountID>` is globally unique and always identifies the
* same identity.
*
* If the adapter was unable to authenticate an identity, it should return
* `null`.
@@ -55,9 +31,7 @@ abstract class PhutilAuthAdapter extends Phobject {
* @return string|null Unique account identifier, or `null` if authentication
* failed.
*/
public function getAccountID() {
throw new PhutilMethodNotImplementedException();
}
abstract public function getAccountID();
/**

View File

@@ -51,17 +51,13 @@ final class PhutilGitHubAuthAdapter extends PhutilOAuthAuthAdapter {
protected function loadOAuthAccountData() {
$uri = new PhutilURI('https://api.github.com/user');
$uri->replaceQueryParam('access_token', $this->getAccessToken());
$future = new HTTPSFuture($uri);
// NOTE: GitHub requires a User-Agent string.
$future->addHeader('User-Agent', __CLASS__);
// See T13485. Circa early 2020, GitHub has deprecated use of the
// "access_token" URI parameter.
$token_header = sprintf('token %s', $this->getAccessToken());
$future->addHeader('Authorization', $token_header);
list($body) = $future->resolvex();
try {

View File

@@ -13,23 +13,8 @@ final class PhutilGoogleAuthAdapter extends PhutilOAuthAuthAdapter {
return 'google.com';
}
protected function newAccountIdentifiers() {
$identifiers = array();
$account_id = $this->getOAuthAccountData('id');
if ($account_id !== null) {
$account_id = sprintf(
'id(%s)',
$account_id);
$identifiers[] = $this->newAccountIdentifier($account_id);
}
$email = $this->getAccountEmail();
if ($email !== null) {
$identifiers[] = $this->newAccountIdentifier($email);
}
return $identifiers;
public function getAccountID() {
return $this->getAccountEmail();
}
public function getAccountEmail() {

View File

@@ -10,6 +10,7 @@ final class PhutilJIRAAuthAdapter extends PhutilOAuth1AuthAdapter {
private $jiraBaseURI;
private $adapterDomain;
private $currentSession;
private $userInfo;
public function setJIRABaseURI($jira_base_uri) {
@@ -21,33 +22,12 @@ final class PhutilJIRAAuthAdapter extends PhutilOAuth1AuthAdapter {
return $this->jiraBaseURI;
}
protected function newAccountIdentifiers() {
public function getAccountID() {
// Make sure the handshake is finished; this method is used for its
// side effect by Auth providers.
$this->getHandshakeData();
$info = $this->getUserInfo();
// See T13493. Older versions of JIRA provide a "key" with a username or
// email address. Newer versions of JIRA provide a GUID "accountId".
// Intermediate versions of JIRA provide both.
$identifiers = array();
$account_key = idx($info, 'key');
if ($account_key !== null) {
$identifiers[] = $this->newAccountIdentifier($account_key);
}
$account_id = idx($info, 'accountId');
if ($account_id !== null) {
$identifiers[] = $this->newAccountIdentifier(
sprintf(
'accountId(%s)',
$account_id));
}
return $identifiers;
return idx($this->getUserInfo(), 'key');
}
public function getAccountName() {
@@ -105,36 +85,23 @@ final class PhutilJIRAAuthAdapter extends PhutilOAuth1AuthAdapter {
private function getUserInfo() {
if ($this->userInfo === null) {
$this->userInfo = $this->newUserInfo();
$this->currentSession = $this->newJIRAFuture('rest/auth/1/session', 'GET')
->resolveJSON();
// The session call gives us the username, but not the user key or other
// information. Make a second call to get additional information.
$params = array(
'username' => $this->currentSession['name'],
);
$this->userInfo = $this->newJIRAFuture('rest/api/2/user', 'GET', $params)
->resolveJSON();
}
return $this->userInfo;
}
private function newUserInfo() {
// See T13493. Try a relatively modern (circa early 2020) API call first.
try {
return $this->newJIRAFuture('rest/api/3/myself', 'GET')
->resolveJSON();
} catch (Exception $ex) {
// If we failed the v3 call, assume the server version is too old
// to support this API and fall back to trying the older method.
}
$session = $this->newJIRAFuture('rest/auth/1/session', 'GET')
->resolveJSON();
// The session call gives us the username, but not the user key or other
// information. Make a second call to get additional information.
$params = array(
'username' => $session['name'],
);
return $this->newJIRAFuture('rest/api/2/user', 'GET', $params)
->resolveJSON();
}
public static function newJIRAKeypair() {
$config = array(
'digest_alg' => 'sha512',

View File

@@ -156,7 +156,7 @@ abstract class PhutilOAuth1AuthAdapter extends PhutilAuthAdapter {
$authorize_token_uri = new PhutilURI($this->getAuthorizeTokenURI());
$authorize_token_uri->replaceQueryParam('oauth_token', $this->getToken());
return phutil_string_cast($authorize_token_uri);
return (string)$authorize_token_uri;
}
protected function finishOAuthHandshake() {

View File

@@ -73,7 +73,7 @@ final class PhabricatorAuthApplication extends PhabricatorApplication {
'session/downgrade/'
=> 'PhabricatorAuthDowngradeSessionController',
'enroll/' => array(
'(?:(?P<pageKey>[^/]+)/)?'
'(?:(?P<pageKey>[^/]+)/)?(?:(?P<formSaved>saved)/)?'
=> 'PhabricatorAuthNeedsMultiFactorController',
),
'sshkey/' => array(

View File

@@ -63,13 +63,6 @@ final class PhabricatorCookies extends Phobject {
const COOKIE_INVITE = 'invite';
/**
* Stores a workflow completion across a redirect-after-POST following a
* form submission. This can be used to show "Changes Saved" messages.
*/
const COOKIE_SUBMIT = 'phfrm';
/* -( Client ID Cookie )--------------------------------------------------- */

View File

@@ -197,6 +197,22 @@ abstract class PhabricatorAuthController extends PhabricatorController {
return array($account, $provider, $response);
}
$other_account = id(new PhabricatorExternalAccount())->loadAllWhere(
'accountType = %s AND accountDomain = %s AND accountID = %s
AND id != %d',
$account->getAccountType(),
$account->getAccountDomain(),
$account->getAccountID(),
$account->getID());
if ($other_account) {
$response = $this->renderError(
pht(
'The account you are attempting to register with already belongs '.
'to another user.'));
return array($account, $provider, $response);
}
$config = $account->getProviderConfig();
if (!$config->getIsEnabled()) {
$response = $this->renderError(

View File

@@ -116,21 +116,14 @@ final class PhabricatorAuthLoginController
}
} else {
// If the user already has a linked account on this provider, prevent
// them from linking a second account. This can happen if they swap
// logins and then refresh the account link.
// There's no technical reason we can't allow you to link multiple
// accounts from a single provider; disallowing this is currently a
// product deciison. See T2549.
// If the user already has a linked account of this type, prevent them
// from linking a second account. This can happen if they swap logins
// and then refresh the account link. See T6707. We will eventually
// allow this after T2549.
$existing_accounts = id(new PhabricatorExternalAccountQuery())
->setViewer($viewer)
->withUserPHIDs(array($viewer->getPHID()))
->withProviderConfigPHIDs(
array(
$provider->getProviderConfigPHID(),
))
->withAccountTypes(array($account->getAccountType()))
->execute();
if ($existing_accounts) {
return $this->renderError(

View File

@@ -465,6 +465,7 @@ final class PhabricatorAuthRegisterController
if (!$is_setup) {
$account->setUserPHID($user->getPHID());
$provider->willRegisterAccount($account);
$account->save();
}

View File

@@ -67,7 +67,7 @@ final class PhabricatorAuthUnlinkController
->setWorkflowKey($workflow_key)
->requireHighSecurityToken($viewer, $request, $done_uri);
$account->unlinkAccount();
$account->delete();
id(new PhabricatorAuthSessionEngine())->terminateLoginSessions(
$viewer,

View File

@@ -181,12 +181,6 @@ final class PhabricatorAuthPasswordEngine
$normal_password = phutil_utf8_strtolower($raw_password);
if (strlen($normal_password) >= $minimum_similarity) {
foreach ($normal_map as $term => $source) {
// See T2312. This may be required if the term list includes numeric
// strings like "12345", which will be cast to integers when used as
// array keys.
$term = phutil_string_cast($term);
if (strpos($term, $normal_password) === false &&
strpos($normal_password, $term) === false) {
continue;

View File

@@ -56,12 +56,9 @@ final class PhabricatorAuthManagementLDAPWorkflow
$console->writeOut("\n");
$console->writeOut("%s\n", pht('Connecting to LDAP...'));
$account_ids = $adapter->getAccountIdentifiers();
if ($account_ids) {
$value_list = mpull($account_ids, 'getIdentifierRaw');
$value_list = implode(', ', $value_list);
$console->writeOut("%s\n", pht('Found LDAP Account: %s', $value_list));
$account_id = $adapter->getAccountID();
if ($account_id) {
$console->writeOut("%s\n", pht('Found LDAP Account: %s', $account_id));
} else {
$console->writeOut("%s\n", pht('Unable to find LDAP account!'));
}

View File

@@ -18,6 +18,16 @@ final class PhabricatorAuthManagementRefreshWorkflow
'param' => 'user',
'help' => pht('Refresh tokens for a given user.'),
),
array(
'name' => 'type',
'param' => 'provider',
'help' => pht('Refresh tokens for a given provider type.'),
),
array(
'name' => 'domain',
'param' => 'domain',
'help' => pht('Refresh tokens for a given domain.'),
),
));
}
@@ -47,6 +57,17 @@ final class PhabricatorAuthManagementRefreshWorkflow
}
}
$type = $args->getArg('type');
if (strlen($type)) {
$query->withAccountTypes(array($type));
}
$domain = $args->getArg('domain');
if (strlen($domain)) {
$query->withAccountDomains(array($domain));
}
$accounts = $query->execute();
if (!$accounts) {
@@ -61,24 +82,25 @@ final class PhabricatorAuthManagementRefreshWorkflow
}
$providers = PhabricatorAuthProvider::getAllEnabledProviders();
$providers = mpull($providers, null, 'getProviderConfigPHID');
foreach ($accounts as $account) {
$console->writeOut(
"%s\n",
pht(
'Refreshing account #%d.',
$account->getID()));
'Refreshing account #%d (%s/%s).',
$account->getID(),
$account->getAccountType(),
$account->getAccountDomain()));
$config_phid = $account->getProviderConfigPHID();
if (empty($providers[$config_phid])) {
$key = $account->getProviderKey();
if (empty($providers[$key])) {
$console->writeOut(
"> %s\n",
pht('Skipping, provider is not enabled or does not exist.'));
continue;
}
$provider = $providers[$config_phid];
$provider = $providers[$key];
if (!($provider instanceof PhabricatorOAuth2AuthProvider)) {
$console->writeOut(
"> %s\n",

View File

@@ -20,10 +20,6 @@ abstract class PhabricatorAuthProvider extends Phobject {
return $this->providerConfig;
}
public function getProviderConfigPHID() {
return $this->getProviderConfig()->getPHID();
}
public function getConfigurationHelp() {
return null;
}
@@ -190,86 +186,44 @@ abstract class PhabricatorAuthProvider extends Phobject {
return;
}
final protected function newExternalAccountForIdentifiers(
array $identifiers) {
assert_instances_of($identifiers, 'PhabricatorExternalAccountIdentifier');
if (!$identifiers) {
throw new Exception(
pht(
'Authentication provider (of class "%s") is attempting to '.
'load or create an external account, but provided no account '.
'identifiers.',
get_class($this)));
}
$config = $this->getProviderConfig();
$viewer = PhabricatorUser::getOmnipotentUser();
$raw_identifiers = mpull($identifiers, 'getIdentifierRaw');
$accounts = id(new PhabricatorExternalAccountQuery())
->setViewer($viewer)
->withProviderConfigPHIDs(array($config->getPHID()))
->withRawAccountIdentifiers($raw_identifiers)
->needAccountIdentifiers(true)
->execute();
if (!$accounts) {
$account = $this->newExternalAccount();
} else if (count($accounts) === 1) {
$account = head($accounts);
} else {
throw new Exception(
pht(
'Authentication provider (of class "%s") is attempting to load '.
'or create an external account, but provided a list of '.
'account identifiers which map to more than one account: %s.',
get_class($this),
implode(', ', $raw_identifiers)));
}
// See T13493. Add all the identifiers to the account. In the case where
// an account initially has a lower-quality identifier (like an email
// address) and later adds a higher-quality identifier (like a GUID), this
// allows us to automatically upgrade toward the higher-quality identifier
// and survive API changes which remove the lower-quality identifier more
// gracefully.
foreach ($identifiers as $identifier) {
$account->appendIdentifier($identifier);
}
return $this->didUpdateAccount($account);
public function willRegisterAccount(PhabricatorExternalAccount $account) {
return;
}
final protected function newExternalAccountForUser(PhabricatorUser $user) {
$config = $this->getProviderConfig();
protected function loadOrCreateAccount($account_id) {
if (!strlen($account_id)) {
throw new Exception(pht('Empty account ID!'));
}
// When a user logs in with a provider like username/password, they
// always already have a Phabricator account (since there's no way they
// could have a username otherwise).
$adapter = $this->getAdapter();
$adapter_class = get_class($adapter);
// These users should never go to registration, so we're building a
// dummy "external account" which just links directly back to their
// internal account.
if (!strlen($adapter->getAdapterType())) {
throw new Exception(
pht(
"AuthAdapter (of class '%s') has an invalid implementation: ".
"no adapter type.",
$adapter_class));
}
$account = id(new PhabricatorExternalAccountQuery())
->setViewer($user)
->withProviderConfigPHIDs(array($config->getPHID()))
->withUserPHIDs(array($user->getPHID()))
->executeOne();
if (!strlen($adapter->getAdapterDomain())) {
throw new Exception(
pht(
"AuthAdapter (of class '%s') has an invalid implementation: ".
"no adapter domain.",
$adapter_class));
}
$account = id(new PhabricatorExternalAccount())->loadOneWhere(
'accountType = %s AND accountDomain = %s AND accountID = %s',
$adapter->getAdapterType(),
$adapter->getAdapterDomain(),
$account_id);
if (!$account) {
$account = $this->newExternalAccount()
->setUserPHID($user->getPHID());
->setAccountID($account_id);
}
return $this->didUpdateAccount($account);
}
private function didUpdateAccount(PhabricatorExternalAccount $account) {
$adapter = $this->getAdapter();
$account->setUsername($adapter->getAccountName());
$account->setRealName($adapter->getAccountRealName());
$account->setEmail($adapter->getAccountEmail());
@@ -286,7 +240,6 @@ abstract class PhabricatorAuthProvider extends Phobject {
// file entry for it, but there's no convenient way to do this with
// PhabricatorFile right now. The storage will get shared, so the impact
// here is negligible.
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$image_file = PhabricatorFile::newFromFileDownload(
$image_uri,
@@ -352,23 +305,10 @@ abstract class PhabricatorAuthProvider extends Phobject {
$config = $this->getProviderConfig();
$adapter = $this->getAdapter();
$account = id(new PhabricatorExternalAccount())
->setProviderConfigPHID($config->getPHID())
->attachAccountIdentifiers(array());
// TODO: Remove this when these columns are removed. They no longer have
// readers or writers (other than this callsite).
$account
return id(new PhabricatorExternalAccount())
->setAccountType($adapter->getAdapterType())
->setAccountDomain($adapter->getAdapterDomain());
// TODO: Remove this when "accountID" is removed; the column is not
// nullable.
$account->setAccountID('');
return $account;
->setAccountDomain($adapter->getAdapterDomain())
->setProviderConfigPHID($config->getPHID());
}
public function getLoginOrder() {

View File

@@ -335,7 +335,7 @@ final class PhabricatorJIRAAuthProvider
public function getDoorkeeperURIRef(PhutilURI $uri) {
$uri_string = phutil_string_cast($uri);
$pattern = '((https?://\S+?)/browse/([A-Z][A-Z0-9]*-[1-9]\d*))';
$pattern = '((https?://\S+?)/browse/([A-Z]+-[1-9]\d*))';
$matches = null;
if (!preg_match($pattern, $uri_string, $matches)) {
return null;

View File

@@ -164,7 +164,7 @@ final class PhabricatorLDAPAuthProvider extends PhabricatorAuthProvider {
// See T3351.
DarkConsoleErrorLogPluginAPI::enableDiscardMode();
$identifiers = $adapter->getAccountIdentifiers();
$account_id = $adapter->getAccountID();
DarkConsoleErrorLogPluginAPI::disableDiscardMode();
} else {
throw new Exception(pht('Username and password are required!'));
@@ -180,9 +180,7 @@ final class PhabricatorLDAPAuthProvider extends PhabricatorAuthProvider {
}
}
$account = $this->newExternalAccountForIdentifiers($identifiers);
return array($account, $response);
return array($this->loadOrCreateAccount($account_id), $response);
}

View File

@@ -100,13 +100,13 @@ abstract class PhabricatorOAuth1AuthProvider
// an access token.
try {
$identifiers = $adapter->getAccountIdentifiers();
$account_id = $adapter->getAccountID();
} catch (Exception $ex) {
// TODO: Handle this in a more user-friendly way.
throw $ex;
}
if (!$identifiers) {
if (!strlen($account_id)) {
$response = $controller->buildProviderErrorResponse(
$this,
pht(
@@ -115,9 +115,7 @@ abstract class PhabricatorOAuth1AuthProvider
return array($account, $response);
}
$account = $this->newExternalAccountForIdentifiers($identifiers);
return array($account, $response);
return array($this->loadOrCreateAccount($account_id), $response);
}
public function processEditForm(

View File

@@ -80,13 +80,13 @@ abstract class PhabricatorOAuth2AuthProvider
// an access token.
try {
$identifiers = $adapter->getAccountIdentifiers();
$account_id = $adapter->getAccountID();
} catch (Exception $ex) {
// TODO: Handle this in a more user-friendly way.
throw $ex;
}
if (!$identifiers) {
if (!strlen($account_id)) {
$response = $controller->buildProviderErrorResponse(
$this,
pht(
@@ -95,9 +95,7 @@ abstract class PhabricatorOAuth2AuthProvider
return array($account, $response);
}
$account = $this->newExternalAccountForIdentifiers($identifiers);
return array($account, $response);
return array($this->loadOrCreateAccount($account_id), $response);
}
public function processEditForm(
@@ -199,7 +197,7 @@ abstract class PhabricatorOAuth2AuthProvider
PhabricatorExternalAccount $account,
$force_refresh = false) {
if ($account->getProviderConfigPHID() !== $this->getProviderConfigPHID()) {
if ($account->getProviderKey() !== $this->getProviderKey()) {
throw new Exception(pht('Account does not match provider!'));
}

View File

@@ -332,7 +332,7 @@ final class PhabricatorPasswordAuthProvider extends PhabricatorAuthProvider {
->setObject($user);
if ($engine->isValidPassword($envelope)) {
$account = $this->newExternalAccountForUser($user);
$account = $this->loadOrCreateAccount($user->getPHID());
$log_user = $user;
}
}
@@ -366,6 +366,16 @@ final class PhabricatorPasswordAuthProvider extends PhabricatorAuthProvider {
return true;
}
protected function willSaveAccount(PhabricatorExternalAccount $account) {
parent::willSaveAccount($account);
$account->setUserPHID($account->getAccountID());
}
public function willRegisterAccount(PhabricatorExternalAccount $account) {
parent::willRegisterAccount($account);
$account->setAccountID($account->getUserPHID());
}
public static function getPasswordProvider() {
$providers = self::getAllEnabledProviders();
@@ -392,5 +402,4 @@ final class PhabricatorPasswordAuthProvider extends PhabricatorAuthProvider {
public function shouldAllowEmailTrustConfiguration() {
return false;
}
}

View File

@@ -1,94 +0,0 @@
<?php
final class PhabricatorExternalAccountIdentifierQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $providerConfigPHIDs;
private $externalAccountPHIDs;
private $rawIdentifiers;
public function withIDs($ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withProviderConfigPHIDs(array $phids) {
$this->providerConfigPHIDs = $phids;
return $this;
}
public function withExternalAccountPHIDs(array $phids) {
$this->externalAccountPHIDs = $phids;
return $this;
}
public function withRawIdentifiers(array $identifiers) {
$this->rawIdentifiers = $identifiers;
return $this;
}
public function newResultObject() {
return new PhabricatorExternalAccountIdentifier();
}
protected function loadPage() {
return $this->loadStandardPage($this->newResultObject());
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid IN (%Ls)',
$this->phids);
}
if ($this->providerConfigPHIDs !== null) {
$where[] = qsprintf(
$conn,
'providerConfigPHID IN (%Ls)',
$this->providerConfigPHIDs);
}
if ($this->externalAccountPHIDs !== null) {
$where[] = qsprintf(
$conn,
'externalAccountPHID IN (%Ls)',
$this->externalAccountPHIDs);
}
if ($this->rawIdentifiers !== null) {
$hashes = array();
foreach ($this->rawIdentifiers as $raw_identifier) {
$hashes[] = PhabricatorHash::digestForIndex($raw_identifier);
}
$where[] = qsprintf(
$conn,
'identifierHash IN (%Ls)',
$hashes);
}
return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorPeopleApplication';
}
}

View File

@@ -15,18 +15,34 @@ final class PhabricatorExternalAccountQuery
private $ids;
private $phids;
private $accountTypes;
private $accountDomains;
private $accountIDs;
private $userPHIDs;
private $needImages;
private $accountSecrets;
private $providerConfigPHIDs;
private $needAccountIdentifiers;
private $rawAccountIdentifiers;
public function withUserPHIDs(array $user_phids) {
$this->userPHIDs = $user_phids;
return $this;
}
public function withAccountIDs(array $account_ids) {
$this->accountIDs = $account_ids;
return $this;
}
public function withAccountDomains(array $account_domains) {
$this->accountDomains = $account_domains;
return $this;
}
public function withAccountTypes(array $account_types) {
$this->accountTypes = $account_types;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
@@ -47,21 +63,11 @@ final class PhabricatorExternalAccountQuery
return $this;
}
public function needAccountIdentifiers($need) {
$this->needAccountIdentifiers = $need;
return $this;
}
public function withProviderConfigPHIDs(array $phids) {
$this->providerConfigPHIDs = $phids;
return $this;
}
public function withRawAccountIdentifiers(array $identifiers) {
$this->rawAccountIdentifiers = $identifiers;
return $this;
}
public function newResultObject() {
return new PhabricatorExternalAccount();
}
@@ -126,23 +132,6 @@ final class PhabricatorExternalAccountQuery
}
}
if ($this->needAccountIdentifiers) {
$account_phids = mpull($accounts, 'getPHID');
$identifiers = id(new PhabricatorExternalAccountIdentifierQuery())
->setViewer($viewer)
->setParentQuery($this)
->withExternalAccountPHIDs($account_phids)
->execute();
$identifiers = mgroup($identifiers, 'getExternalAccountPHID');
foreach ($accounts as $account) {
$account_phid = $account->getPHID();
$account_identifiers = idx($identifiers, $account_phid, array());
$account->attachAccountIdentifiers($account_identifiers);
}
}
return $accounts;
}
@@ -152,98 +141,62 @@ final class PhabricatorExternalAccountQuery
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'account.id IN (%Ld)',
'id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'account.phid IN (%Ls)',
'phid IN (%Ls)',
$this->phids);
}
if ($this->accountTypes !== null) {
$where[] = qsprintf(
$conn,
'accountType IN (%Ls)',
$this->accountTypes);
}
if ($this->accountDomains !== null) {
$where[] = qsprintf(
$conn,
'accountDomain IN (%Ls)',
$this->accountDomains);
}
if ($this->accountIDs !== null) {
$where[] = qsprintf(
$conn,
'accountID IN (%Ls)',
$this->accountIDs);
}
if ($this->userPHIDs !== null) {
$where[] = qsprintf(
$conn,
'account.userPHID IN (%Ls)',
'userPHID IN (%Ls)',
$this->userPHIDs);
}
if ($this->accountSecrets !== null) {
$where[] = qsprintf(
$conn,
'account.accountSecret IN (%Ls)',
'accountSecret IN (%Ls)',
$this->accountSecrets);
}
if ($this->providerConfigPHIDs !== null) {
$where[] = qsprintf(
$conn,
'account.providerConfigPHID IN (%Ls)',
'providerConfigPHID IN (%Ls)',
$this->providerConfigPHIDs);
// If we have a list of ProviderConfig PHIDs and are joining the
// identifiers table, also include the list as an additional constraint
// on the identifiers table.
// This does not change the query results (an Account and its
// Identifiers always have the same ProviderConfig PHID) but it allows
// us to use keys on the Identifier table more efficiently.
if ($this->shouldJoinIdentifiersTable()) {
$where[] = qsprintf(
$conn,
'identifier.providerConfigPHID IN (%Ls)',
$this->providerConfigPHIDs);
}
}
if ($this->rawAccountIdentifiers !== null) {
$hashes = array();
foreach ($this->rawAccountIdentifiers as $raw_identifier) {
$hashes[] = PhabricatorHash::digestForIndex($raw_identifier);
}
$where[] = qsprintf(
$conn,
'identifier.identifierHash IN (%Ls)',
$hashes);
}
return $where;
}
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
$joins = parent::buildJoinClauseParts($conn);
if ($this->shouldJoinIdentifiersTable()) {
$joins[] = qsprintf(
$conn,
'JOIN %R identifier ON account.phid = identifier.externalAccountPHID',
new PhabricatorExternalAccountIdentifier());
}
return $joins;
}
protected function shouldJoinIdentifiersTable() {
return ($this->rawAccountIdentifiers !== null);
}
protected function shouldGroupQueryResultRows() {
if ($this->shouldJoinIdentifiersTable()) {
return true;
}
return parent::shouldGroupQueryResultRows();
}
protected function getPrimaryTableAlias() {
return 'account';
}
public function getQueryApplicationClass() {
return 'PhabricatorPeopleApplication';
}

View File

@@ -80,7 +80,7 @@ final class PhabricatorAuthSSHPrivateKey extends Phobject {
if (!$err) {
if ($passphrase) {
execx(
'ssh-keygen -p -P %P -N %s -f %R',
'ssh-keygen -y -P %P -N %s -f %R',
$passphrase,
'',
$tmp);

View File

@@ -4,8 +4,7 @@ final class PhabricatorAuthProviderConfig
extends PhabricatorAuthDAO
implements
PhabricatorApplicationTransactionInterface,
PhabricatorPolicyInterface,
PhabricatorDestructibleInterface {
PhabricatorPolicyInterface {
protected $providerClass;
protected $providerType;
@@ -141,33 +140,4 @@ final class PhabricatorAuthProviderConfig
return false;
}
/* -( PhabricatorDestructibleInterface )----------------------------------- */
public function destroyObjectPermanently(
PhabricatorDestructionEngine $engine) {
$viewer = $engine->getViewer();
$config_phid = $this->getPHID();
$accounts = id(new PhabricatorExternalAccountQuery())
->setViewer($viewer)
->withProviderConfigPHIDs(array($config_phid))
->newIterator();
foreach ($accounts as $account) {
$engine->destroyObject($account);
}
$identifiers = id(new PhabricatorExternalAccountIdentifierQuery())
->setViewer($viewer)
->withProviderConfigPHIDs(array($config_phid))
->newIterator();
foreach ($identifiers as $identifier) {
$engine->destroyObject($identifier);
}
$this->delete();
}
}

View File

@@ -37,6 +37,8 @@ final class PhabricatorAuthAccountView extends AphrontView {
$use_name = $username;
} else if (strlen($realname)) {
$use_name = $realname;
} else {
$use_name = $account->getAccountID();
}
$content[] = phutil_tag(
@@ -59,6 +61,8 @@ final class PhabricatorAuthAccountView extends AphrontView {
),
array(
$prov_name,
" \xC2\xB7 ",
$account->getAccountID(),
));
$account_uri = $account->getAccountURI();

View File

@@ -849,8 +849,8 @@ final class PhutilICSParser extends Phobject {
);
// Load the map of Windows timezones.
$root_path = dirname(phutil_get_library_root('phabricator'));
$windows_path = $root_path.'/resources/timezones/windows-timezones.json';
$root_path = dirname(phutil_get_library_root('phutil'));
$windows_path = $root_path.'/resources/timezones/windows_timezones.json';
$windows_data = Filesystem::readFile($windows_path);
$windows_zones = phutil_json_decode($windows_data);

View File

@@ -82,7 +82,6 @@ final class CelerityDefaultPostprocessor
'alphablack' => '0,0,0',
// Base Greys
'thingreyborder' => '#dadee8',
'lightgreyborder' => '#C7CCD9',
'greyborder' => '#A1A6B0',
'darkgreyborder' => '#676A70',
@@ -208,9 +207,6 @@ final class CelerityDefaultPostprocessor
// Usually light yellow
'gentle.highlight' => '#fdf3da',
'gentle.highlight.border' => '#c9b8a8',
'gentle.highlight.background' => '#fffdf6',
'highlight.bright' => '#fdf320',
'paste.content' => '#fffef5',
'paste.border' => '#e9dbcd',
@@ -244,9 +240,6 @@ final class CelerityDefaultPostprocessor
'document.border' => '#dedee1',
'delete-color' => '#c0392b',
'create-color' => '#139543',
);
}

View File

@@ -17,9 +17,6 @@ final class CelerityRedGreenPostprocessor
'new-bright' => 'rgba(152, 207, 235, .35)',
'old-background' => 'rgba(250, 212, 175, .3)',
'old-bright' => 'rgba(250, 212, 175, .55)',
'delete-color' => '#e67e22',
'create-color' => '#2980b9',
);
}

View File

@@ -100,17 +100,9 @@ final class PhabricatorConduitAPIController
}
} catch (Exception $ex) {
$result = null;
if ($ex instanceof ConduitException) {
$error_code = 'ERR-CONDUIT-CALL';
} else {
$error_code = 'ERR-CONDUIT-CORE';
// See T13581. When a Conduit method raises an uncaught exception
// other than a "ConduitException", log it.
phlog($ex);
}
$error_code = ($ex instanceof ConduitException
? 'ERR-CONDUIT-CALL'
: 'ERR-CONDUIT-CORE');
$error_info = $ex->getMessage();
}
@@ -142,17 +134,9 @@ final class PhabricatorConduitAPIController
$method_implementation);
case 'json':
default:
$response = id(new AphrontJSONResponse())
return id(new AphrontJSONResponse())
->setAddJSONShield(false)
->setContent($response->toDictionary());
$capabilities = $this->getConduitCapabilities();
if ($capabilities) {
$capabilities = implode(' ', $capabilities);
$response->addHeader('X-Conduit-Capabilities', $capabilities);
}
return $response;
}
}
@@ -732,14 +716,5 @@ final class PhabricatorConduitAPIController
return false;
}
private function getConduitCapabilities() {
$capabilities = array();
if (AphrontRequestStream::supportsGzip()) {
$capabilities[] = 'gzip';
}
return $capabilities;
}
}

View File

@@ -142,8 +142,6 @@ abstract class PhabricatorConduitController extends PhabricatorController {
$parts[] = '--conduit-token ';
$parts[] = phutil_tag('strong', array(), '<conduit-token>');
$parts[] = ' ';
$parts[] = '--';
$parts[] = ' ';
$parts[] = $method->getAPIMethodName();

View File

@@ -21,12 +21,6 @@ final class PhabricatorConduitCallManagementWorkflow
'File to read parameters from, or "-" to read from '.
'stdin.'),
),
array(
'name' => 'local',
'help' => pht(
'Force the request to execute in this process, rather than '.
'proxying to another host in the cluster.'),
),
array(
'name' => 'as',
'param' => 'username',
@@ -81,17 +75,9 @@ final class PhabricatorConduitCallManagementWorkflow
$params = phutil_json_decode($input_json);
$call = id(new ConduitCall($method, $params))
->setUser($actor);
$api_request = $call->getAPIRequest();
$is_local = $args->getArg('local');
if ($is_local) {
$api_request->setIsClusterRequest(true);
}
$result = $call->execute();
$result = id(new ConduitCall($method, $params))
->setUser($actor)
->execute();
$output = array(
'result' => $result,

View File

@@ -120,21 +120,9 @@ abstract class ConduitAPIMethod
public function executeMethod(ConduitAPIRequest $request) {
$this->setViewer($request->getUser());
$client = $this->newConduitCallProxyClient($request);
if ($client) {
// We're proxying, so just make an intracluster call.
return $client->callMethodSynchronous(
$this->getAPIMethodName(),
$request->getAllParameters());
}
return $this->execute($request);
}
protected function newConduitCallProxyClient(ConduitAPIRequest $request) {
return null;
}
abstract public function getAPIMethodName();
/**

View File

@@ -129,9 +129,8 @@ abstract class ConduitParameterType extends Phobject {
'true' => true,
);
$normal_value = phutil_utf8_strtolower($value);
if (!$strict && is_string($value) && isset($bool_strings[$normal_value])) {
$value = $bool_strings[$normal_value];
if (!$strict && is_string($value) && isset($bool_strings[$value])) {
$value = $bool_strings[$value];
} else if (!is_bool($value)) {
$this->raiseValidationException(
$request,

View File

@@ -51,10 +51,6 @@ final class ConduitAPIRequest extends Phobject {
return $this->user;
}
public function getViewer() {
return $this->getUser();
}
public function setOAuthToken(
PhabricatorOAuthServerAccessToken $oauth_token) {
$this->oauthToken = $oauth_token;

View File

@@ -37,8 +37,13 @@ final class PhabricatorConfigApplication extends PhabricatorApplication {
public function getRoutes() {
return array(
'/config/' => array(
'' => 'PhabricatorConfigConsoleController',
'' => 'PhabricatorConfigListController',
'application/' => 'PhabricatorConfigApplicationController',
'all/' => 'PhabricatorConfigAllController',
'history/' => 'PhabricatorConfigHistoryController',
'edit/(?P<key>[\w\.\-]+)/' => 'PhabricatorConfigEditController',
'group/(?P<key>[^/]+)/' => 'PhabricatorConfigGroupController',
'version/' => 'PhabricatorConfigVersionController',
'database/'.
'(?:(?P<ref>[^/]+)/'.
'(?:(?P<database>[^/]+)/'.
@@ -58,7 +63,7 @@ final class PhabricatorConfigApplication extends PhabricatorApplication {
'purge/' => 'PhabricatorConfigPurgeCacheController',
),
'module/' => array(
'(?:(?P<module>[^/]+)/)?' => 'PhabricatorConfigModuleController',
'(?P<module>[^/]+)/' => 'PhabricatorConfigModuleController',
),
'cluster/' => array(
'databases/' => 'PhabricatorConfigClusterDatabasesController',
@@ -66,12 +71,6 @@ final class PhabricatorConfigApplication extends PhabricatorApplication {
'repositories/' => 'PhabricatorConfigClusterRepositoriesController',
'search/' => 'PhabricatorConfigClusterSearchController',
),
'settings/' => array(
'' => 'PhabricatorConfigSettingsListController',
'(?P<filter>advanced|all)/'
=> 'PhabricatorConfigSettingsListController',
'history/' => 'PhabricatorConfigSettingsHistoryController',
),
),
);
}

View File

@@ -53,23 +53,20 @@ final class PhabricatorAuthSetupCheck extends PhabricatorSetupCheck {
"\n\n".
'Leaving your authentication provider configuration unlocked '.
'increases the damage that a compromised administrator account can '.
'do to your install. For example, an attacker who compromises an '.
'administrator account can change authentication providers to point '.
'at a server they control and attempt to intercept usernames and '.
'do to your install, by, for example, changing the authentication '.
'provider to a server they control and intercepting usernames and '.
'passwords.'.
"\n\n".
'To prevent this attack, you should configure authentication, and '.
'then lock the configuration by running "bin/auth lock" from the '.
'command line. This will prevent changing the authentication config '.
'without first running "bin/auth unlock".');
'To prevent this attack, you should configure your authentication '.
'providers, and then lock the configuration by doing `%s` '.
'from the command line. This will prevent changing the '.
'authentication provider config without first doing `%s`.',
'bin/auth lock',
'bin/auth unlock');
$this
->newIssue('auth.config-unlocked')
->setShortName(pht('Auth Config Unlocked'))
->setName(pht('Authenticaton Configuration Unlocked'))
->setSummary(
pht(
'Authentication configuration is currently unlocked. Once you '.
'finish configuring authentication, you should lock it.'))
->setName(pht('Authenticaton Provider Configuration Unlocked'))
->setMessage($message)
->addRelatedPhabricatorConfig('auth.lock-config')
->addCommand(

View File

@@ -8,21 +8,14 @@ final class PhabricatorDaemonsSetupCheck extends PhabricatorSetupCheck {
protected function executeChecks() {
try {
$task_daemons = id(new PhabricatorDaemonLogQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withStatus(PhabricatorDaemonLogQuery::STATUS_ALIVE)
->withDaemonClasses(array('PhabricatorTaskmasterDaemon'))
->setLimit(1)
->execute();
$task_daemon = id(new PhabricatorDaemonLogQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withStatus(PhabricatorDaemonLogQuery::STATUS_ALIVE)
->withDaemonClasses(array('PhabricatorTaskmasterDaemon'))
->setLimit(1)
->execute();
$no_daemons = !$task_daemons;
} catch (Exception $ex) {
// Just skip this warning if the query fails for some reason.
$no_daemons = false;
}
if ($no_daemons) {
if (!$task_daemon) {
$doc_href = PhabricatorEnv::getDoclink('Managing Daemons with phd');
$summary = pht(
@@ -55,17 +48,10 @@ final class PhabricatorDaemonsSetupCheck extends PhabricatorSetupCheck {
$expect_user = PhabricatorEnv::getEnvConfig('phd.user');
if (strlen($expect_user)) {
try {
$all_daemons = id(new PhabricatorDaemonLogQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withStatus(PhabricatorDaemonLogQuery::STATUS_ALIVE)
->execute();
} catch (Exception $ex) {
// If this query fails for some reason, just skip this check.
$all_daemons = array();
}
$all_daemons = id(new PhabricatorDaemonLogQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withStatus(PhabricatorDaemonLogQuery::STATUS_ALIVE)
->execute();
foreach ($all_daemons as $daemon) {
$actual_user = $daemon->getRunningAsUser();
if ($actual_user == $expect_user) {

View File

@@ -322,9 +322,6 @@ final class PhabricatorExtraConfigSetupCheck extends PhabricatorSetupCheck {
'directly supported. Prefixes and other strings may be customized with '.
'"translation.override".');
$phd_reason = pht(
'Use "bin/phd debug ..." to get a detailed daemon execution log.');
$ancient_config += array(
'phid.external-loaders' =>
pht(
@@ -542,9 +539,6 @@ final class PhabricatorExtraConfigSetupCheck extends PhabricatorSetupCheck {
'phd.pid-directory' => pht(
'Phabricator daemons no longer use PID files.'),
'phd.trace' => $phd_reason,
'phd.verbose' => $phd_reason,
);
return $ancient_config;

View File

@@ -58,7 +58,8 @@ final class PhabricatorSecuritySetupCheck extends PhabricatorSetupCheck {
->setName(pht('Alternate File Domain Not Configured'))
->setSummary(
pht(
'Improve security by configuring an alternate file domain.'))
'Increase security (and improve performance) by configuring '.
'a CDN or alternate file domain.'))
->setMessage(
pht(
'Phabricator is currently configured to serve user uploads '.

View File

@@ -45,26 +45,11 @@ final class PhabricatorWebServerSetupCheck extends PhabricatorSetupCheck {
$self_future = id(new HTTPSFuture($base_uri))
->addHeader('X-Phabricator-SelfCheck', 1)
->addHeader('Accept-Encoding', 'gzip')
->setDisableContentDecoding(true)
->setHTTPBasicAuthCredentials(
$expect_user,
new PhutilOpaqueEnvelope($expect_pass))
->setTimeout(5);
if (AphrontRequestStream::supportsGzip()) {
$gzip_uncompressed = str_repeat('Quack! ', 128);
$gzip_compressed = gzencode($gzip_uncompressed);
$gzip_future = id(new HTTPSFuture($base_uri))
->addHeader('X-Phabricator-SelfCheck', 1)
->addHeader('Content-Encoding', 'gzip')
->setTimeout(5)
->setData($gzip_compressed);
} else {
$gzip_future = null;
}
// Make a request to the metadata service available on EC2 instances,
// to test if we're running on a T2 instance in AWS so we can warn that
// this is a bad idea. Outside of AWS, this request will just fail.
@@ -76,16 +61,12 @@ final class PhabricatorWebServerSetupCheck extends PhabricatorSetupCheck {
$self_future,
$ec2_future,
);
if ($gzip_future) {
$futures[] = $gzip_future;
}
$futures = new FutureIterator($futures);
foreach ($futures as $future) {
// Just resolve the futures here.
}
try {
list($body) = $ec2_future->resolvex();
$body = trim($body);
@@ -135,7 +116,7 @@ final class PhabricatorWebServerSetupCheck extends PhabricatorSetupCheck {
->setMessage($message);
} else {
if (function_exists('gzdecode')) {
$body = @gzdecode($body);
$body = gzdecode($body);
} else {
$body = null;
}
@@ -278,107 +259,6 @@ final class PhabricatorWebServerSetupCheck extends PhabricatorSetupCheck {
->setMessage($message);
}
if ($gzip_future) {
$this->checkGzipResponse(
$gzip_future,
$gzip_uncompressed,
$gzip_compressed);
}
}
private function checkGzipResponse(
Future $future,
$uncompressed,
$compressed) {
try {
list($body, $headers) = $future->resolvex();
} catch (Exception $ex) {
return;
}
try {
$structure = phutil_json_decode(trim($body));
} catch (Exception $ex) {
return;
}
$raw_body = idx($structure, 'raw.base64');
$raw_body = @base64_decode($raw_body);
// The server received the exact compressed bytes we expected it to, so
// everything is working great.
if ($raw_body === $compressed) {
return;
}
// If the server received a prefix of the raw uncompressed string, it
// is almost certainly configured to decompress responses inline. Guide
// users to this problem narrowly.
// Otherwise, something is wrong but we don't have much of a clue what.
$message = array();
$message[] = pht(
'Phabricator sent itself a test request that was compressed with '.
'"Content-Encoding: gzip", but received different bytes than it '.
'sent.');
$prefix_len = min(strlen($raw_body), strlen($uncompressed));
if ($prefix_len > 16 && !strncmp($raw_body, $uncompressed, $prefix_len)) {
$message[] = pht(
'The request body that the server received had already been '.
'decompressed. This strongly suggests your webserver is configured '.
'to decompress requests inline, before they reach PHP.');
$message[] = pht(
'If you are using Apache, your server may be configured with '.
'"SetInputFilter DEFLATE". This directive destructively mangles '.
'requests and emits them with "Content-Length" and '.
'"Content-Encoding" headers that no longer match the data in the '.
'request body.');
} else {
$message[] = pht(
'This suggests your webserver is configured to decompress or mangle '.
'compressed requests.');
$message[] = pht(
'The request body Phabricator sent began:');
$message[] = $this->snipBytes($compressed);
$message[] = pht(
'The request body Phabricator received began:');
$message[] = $this->snipBytes($raw_body);
}
$message[] = pht(
'Identify the component in your webserver configuration which is '.
'decompressing or mangling requests and disable it. Phabricator '.
'will not work properly until you do.');
$message = phutil_implode_html("\n\n", $message);
$this->newIssue('webserver.accept-gzip')
->setName(pht('Compressed Requests Not Received Properly'))
->setSummary(
pht(
'Your webserver is not handling compressed request bodies '.
'properly.'))
->setMessage($message);
}
private function snipBytes($raw) {
if (!strlen($raw)) {
$display = pht('<empty>');
} else {
$snip = substr($raw, 0, 24);
$display = phutil_loggable_string($snip);
if (strlen($snip) < strlen($raw)) {
$display .= '...';
}
}
return phutil_tag('tt', array(), $display);
}
}

View File

@@ -0,0 +1,77 @@
<?php
final class PhabricatorConfigAllController
extends PhabricatorConfigController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$db_values = id(new PhabricatorConfigEntry())
->loadAllWhere('namespace = %s', 'default');
$db_values = mpull($db_values, null, 'getConfigKey');
$rows = array();
$options = PhabricatorApplicationConfigOptions::loadAllOptions();
ksort($options);
foreach ($options as $option) {
$key = $option->getKey();
if ($option->getHidden()) {
$value = phutil_tag('em', array(), pht('Hidden'));
} else {
$value = PhabricatorEnv::getEnvConfig($key);
$value = PhabricatorConfigJSON::prettyPrintJSON($value);
}
$db_value = idx($db_values, $key);
$rows[] = array(
phutil_tag(
'a',
array(
'href' => $this->getApplicationURI('edit/'.$key.'/'),
),
$key),
$value,
$db_value && !$db_value->getIsDeleted() ? pht('Customized') : '',
);
}
$table = id(new AphrontTableView($rows))
->setColumnClasses(
array(
'',
'wide',
))
->setHeaders(
array(
pht('Key'),
pht('Value'),
pht('Customized'),
));
$title = pht('Current Settings');
$header = $this->buildHeaderView($title);
$nav = $this->buildSideNavView();
$nav->selectFilter('all/');
$view = $this->buildConfigBoxView(
pht('All Settings'),
$table);
$crumbs = $this->buildApplicationCrumbs()
->addTextCrumb($title)
->setBorder(true);
$content = id(new PHUITwoColumnView())
->setHeader($header)
->setFooter($view);
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->setNavigation($nav)
->appendChild($content);
}
}

Some files were not shown because too many files have changed in this diff Show More