Page Menu
Home
c4science
Search
Configure Global Search
Log In
Files
F113152644
PhabricatorMailReplyHandler.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Subscribers
None
File Metadata
Details
File Info
Storage
Attached
Created
Thu, May 15, 14:51
Size
9 KB
Mime Type
text/x-php
Expires
Sat, May 17, 14:51 (1 d, 23 h)
Engine
blob
Format
Raw Data
Handle
26191363
Attached To
rPH Phabricator
PhabricatorMailReplyHandler.php
View Options
<?php
abstract
class
PhabricatorMailReplyHandler
{
private
$mailReceiver
;
private
$actor
;
private
$excludePHIDs
=
array
();
final
public
function
setMailReceiver
(
$mail_receiver
)
{
$this
->
validateMailReceiver
(
$mail_receiver
);
$this
->
mailReceiver
=
$mail_receiver
;
return
$this
;
}
final
public
function
getMailReceiver
()
{
return
$this
->
mailReceiver
;
}
final
public
function
setActor
(
PhabricatorUser
$actor
)
{
$this
->
actor
=
$actor
;
return
$this
;
}
final
public
function
getActor
()
{
return
$this
->
actor
;
}
final
public
function
setExcludeMailRecipientPHIDs
(
array
$exclude
)
{
$this
->
excludePHIDs
=
$exclude
;
return
$this
;
}
final
public
function
getExcludeMailRecipientPHIDs
()
{
return
$this
->
excludePHIDs
;
}
abstract
public
function
validateMailReceiver
(
$mail_receiver
);
abstract
public
function
getPrivateReplyHandlerEmailAddress
(
PhabricatorObjectHandle
$handle
);
public
function
getReplyHandlerDomain
()
{
return
PhabricatorEnv
::
getEnvConfig
(
'metamta.reply-handler-domain'
);
}
abstract
public
function
getReplyHandlerInstructions
();
abstract
protected
function
receiveEmail
(
PhabricatorMetaMTAReceivedMail
$mail
);
public
function
processEmail
(
PhabricatorMetaMTAReceivedMail
$mail
)
{
$error
=
$this
->
sanityCheckEmail
(
$mail
);
if
(
$error
)
{
if
(
$this
->
shouldSendErrorEmail
(
$mail
))
{
$this
->
sendErrorEmail
(
$error
,
$mail
);
}
return
null
;
}
return
$this
->
receiveEmail
(
$mail
);
}
private
function
sanityCheckEmail
(
PhabricatorMetaMTAReceivedMail
$mail
)
{
$body
=
$mail
->
getCleanTextBody
();
$attachments
=
$mail
->
getAttachments
();
if
(
empty
(
$body
)
&&
empty
(
$attachments
))
{
return
'Empty email body. Email should begin with an !action and / or '
.
'text to comment. Inline replies and signatures are ignored.'
;
}
return
null
;
}
/**
* Only send an error email if the user is talking to just Phabricator. We
* can assume if there is only one To address it is a Phabricator address
* since this code is running and everything.
*/
private
function
shouldSendErrorEmail
(
PhabricatorMetaMTAReceivedMail
$mail
)
{
return
(
count
(
$mail
->
getToAddresses
())
==
1
)
&&
(
count
(
$mail
->
getCCAddresses
())
==
0
);
}
private
function
sendErrorEmail
(
$error
,
PhabricatorMetaMTAReceivedMail
$mail
)
{
$template
=
new
PhabricatorMetaMTAMail
();
$template
->
setSubject
(
'Exception: unable to process your mail request'
);
$template
->
setBody
(
$this
->
buildErrorMailBody
(
$error
,
$mail
));
$template
->
setRelatedPHID
(
$mail
->
getRelatedPHID
());
$phid
=
$this
->
getActor
()->
getPHID
();
$tos
=
array
(
$phid
=>
PhabricatorObjectHandleData
::
loadOneHandle
(
$phid
,
// TODO: This could be cleaner (T603).
PhabricatorUser
::
getOmnipotentUser
()),
);
$mails
=
$this
->
multiplexMail
(
$template
,
$tos
,
array
());
foreach
(
$mails
as
$email
)
{
$email
->
saveAndSend
();
}
return
true
;
}
private
function
buildErrorMailBody
(
$error
,
PhabricatorMetaMTAReceivedMail
$mail
)
{
$original_body
=
$mail
->
getRawTextBody
();
$main_body
=
<<<EOBODY
Your request failed because an error was encoutered while processing it:
ERROR: {$error}
-- Original Body -------------------------------------------------------------
{$original_body}
EOBODY;
$body
=
new
PhabricatorMetaMTAMailBody
();
$body
->
addRawSection
(
$main_body
);
$body
->
addReplySection
(
$this
->
getReplyHandlerInstructions
());
return
$body
->
render
();
}
public
function
supportsPrivateReplies
()
{
return
(
bool
)
$this
->
getReplyHandlerDomain
()
&&
!
$this
->
supportsPublicReplies
();
}
public
function
supportsPublicReplies
()
{
if
(!
PhabricatorEnv
::
getEnvConfig
(
'metamta.public-replies'
))
{
return
false
;
}
if
(!
$this
->
getReplyHandlerDomain
())
{
return
false
;
}
return
(
bool
)
$this
->
getPublicReplyHandlerEmailAddress
();
}
final
public
function
supportsReplies
()
{
return
$this
->
supportsPrivateReplies
()
||
$this
->
supportsPublicReplies
();
}
public
function
getPublicReplyHandlerEmailAddress
()
{
return
null
;
}
final
public
function
getRecipientsSummary
(
array
$to_handles
,
array
$cc_handles
)
{
assert_instances_of
(
$to_handles
,
'PhabricatorObjectHandle'
);
assert_instances_of
(
$cc_handles
,
'PhabricatorObjectHandle'
);
$body
=
''
;
if
(
PhabricatorEnv
::
getEnvConfig
(
'metamta.recipients.show-hints'
))
{
if
(
$to_handles
)
{
$body
.=
"To: "
.
implode
(
', '
,
mpull
(
$to_handles
,
'getName'
)).
"
\n
"
;
}
if
(
$cc_handles
)
{
$body
.=
"Cc: "
.
implode
(
', '
,
mpull
(
$cc_handles
,
'getName'
)).
"
\n
"
;
}
}
return
$body
;
}
final
public
function
multiplexMail
(
PhabricatorMetaMTAMail
$mail_template
,
array
$to_handles
,
array
$cc_handles
)
{
assert_instances_of
(
$to_handles
,
'PhabricatorObjectHandle'
);
assert_instances_of
(
$cc_handles
,
'PhabricatorObjectHandle'
);
$result
=
array
();
// If MetaMTA is configured to always multiplex, skip the single-email
// case.
if
(!
PhabricatorMetaMTAMail
::
shouldMultiplexAllMail
())
{
// If private replies are not supported, simply send one email to all
// recipients and CCs. This covers cases where we have no reply handler,
// or we have a public reply handler.
if
(!
$this
->
supportsPrivateReplies
())
{
$mail
=
clone
$mail_template
;
$mail
->
addTos
(
mpull
(
$to_handles
,
'getPHID'
));
$mail
->
addCCs
(
mpull
(
$cc_handles
,
'getPHID'
));
if
(
$this
->
supportsPublicReplies
())
{
$reply_to
=
$this
->
getPublicReplyHandlerEmailAddress
();
$mail
->
setReplyTo
(
$reply_to
);
}
$result
[]
=
$mail
;
return
$result
;
}
}
$tos
=
mpull
(
$to_handles
,
null
,
'getPHID'
);
$ccs
=
mpull
(
$cc_handles
,
null
,
'getPHID'
);
// Merge all the recipients together. TODO: We could keep the CCs as real
// CCs and send to a "noreply@domain.com" type address, but keep it simple
// for now.
$recipients
=
$tos
+
$ccs
;
// When multiplexing mail, explicitly include To/Cc information in the
// message body and headers.
$mail_template
=
clone
$mail_template
;
$mail_template
->
addPHIDHeaders
(
'X-Phabricator-To'
,
array_keys
(
$tos
));
$mail_template
->
addPHIDHeaders
(
'X-Phabricator-Cc'
,
array_keys
(
$ccs
));
$body
=
$mail_template
->
getBody
();
$body
.=
"
\n
"
;
$body
.=
$this
->
getRecipientsSummary
(
$to_handles
,
$cc_handles
);
foreach
(
$recipients
as
$phid
=>
$recipient
)
{
$mail
=
clone
$mail_template
;
if
(
isset
(
$to_handles
[
$phid
]))
{
$mail
->
addTos
(
array
(
$phid
));
}
else
if
(
isset
(
$cc_handles
[
$phid
]))
{
$mail
->
addCCs
(
array
(
$phid
));
}
else
{
// not good - they should be a to or a cc
continue
;
}
$mail
->
setBody
(
$body
);
$reply_to
=
null
;
if
(!
$reply_to
&&
$this
->
supportsPrivateReplies
())
{
$reply_to
=
$this
->
getPrivateReplyHandlerEmailAddress
(
$recipient
);
}
if
(!
$reply_to
&&
$this
->
supportsPublicReplies
())
{
$reply_to
=
$this
->
getPublicReplyHandlerEmailAddress
();
}
if
(
$reply_to
)
{
$mail
->
setReplyTo
(
$reply_to
);
}
$result
[]
=
$mail
;
}
return
$result
;
}
protected
function
getDefaultPublicReplyHandlerEmailAddress
(
$prefix
)
{
$receiver
=
$this
->
getMailReceiver
();
$receiver_id
=
$receiver
->
getID
();
$domain
=
$this
->
getReplyHandlerDomain
();
// We compute a hash using the object's own PHID to prevent an attacker
// from blindly interacting with objects that they haven't ever received
// mail about by just sending to D1@, D2@, etc...
$hash
=
PhabricatorObjectMailReceiver
::
computeMailHash
(
$receiver
->
getMailKey
(),
$receiver
->
getPHID
());
$address
=
"{$prefix}{$receiver_id}+public+{$hash}@{$domain}"
;
return
$this
->
getSingleReplyHandlerPrefix
(
$address
);
}
protected
function
getSingleReplyHandlerPrefix
(
$address
)
{
$single_handle_prefix
=
PhabricatorEnv
::
getEnvConfig
(
'metamta.single-reply-handler-prefix'
);
return
(
$single_handle_prefix
)
?
$single_handle_prefix
.
'+'
.
$address
:
$address
;
}
protected
function
getDefaultPrivateReplyHandlerEmailAddress
(
PhabricatorObjectHandle
$handle
,
$prefix
)
{
if
(
$handle
->
getType
()
!=
PhabricatorPHIDConstants
::
PHID_TYPE_USER
)
{
// You must be a real user to get a private reply handler address.
return
null
;
}
$user
=
id
(
new
PhabricatorPeopleQuery
())
->
setViewer
(
PhabricatorUser
::
getOmnipotentUser
())
->
withPHIDs
(
array
(
$handle
->
getPHID
()))
->
executeOne
();
if
(!
$user
)
{
// This may happen if a user was subscribed to something, and was then
// deleted.
return
null
;
}
$receiver
=
$this
->
getMailReceiver
();
$receiver_id
=
$receiver
->
getID
();
$user_id
=
$user
->
getID
();
$hash
=
PhabricatorObjectMailReceiver
::
computeMailHash
(
$receiver
->
getMailKey
(),
$handle
->
getPHID
());
$domain
=
$this
->
getReplyHandlerDomain
();
$address
=
"{$prefix}{$receiver_id}+{$user_id}+{$hash}@{$domain}"
;
return
$this
->
getSingleReplyHandlerPrefix
(
$address
);
}
final
protected
function
enhanceBodyWithAttachments
(
$body
,
array
$attachments
,
$format
=
'- {F%d, layout=link}'
)
{
if
(!
$attachments
)
{
return
$body
;
}
$files
=
id
(
new
PhabricatorFile
())
->
loadAllWhere
(
'phid in (%Ls)'
,
$attachments
);
// if we have some text then double return before adding our file list
if
(
$body
)
{
$body
.=
"
\n\n
"
;
}
foreach
(
$files
as
$file
)
{
$file_str
=
sprintf
(
$format
,
$file
->
getID
());
$body
.=
$file_str
.
"
\n
"
;
}
return
rtrim
(
$body
);
}
}
Event Timeline
Log In to Comment