Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F1841818
kolab_sync_message.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
16 KB
Referenced Files
None
Subscribers
None
kolab_sync_message.php
View Options
<?php
/**
+--------------------------------------------------------------------------+
| Kolab Sync (ActiveSync for Kolab) |
| |
| Copyright (C) 2011-2012, Kolab Systems AG <contact@kolabsys.com> |
| |
| This program is free software: you can redistribute it and/or modify |
| it under the terms of the GNU Affero General Public License as published |
| by the Free Software Foundation, either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public License |
| along with this program. If not, see <http://www.gnu.org/licenses/> |
+--------------------------------------------------------------------------+
| Author: Aleksander Machniak <machniak@kolabsys.com> |
+--------------------------------------------------------------------------+
*/
class
kolab_sync_message
{
protected
$headers
=
array
();
protected
$body
;
protected
$ctype
;
protected
$ctype_params
=
array
();
/**
* Constructor
*
* @param string|resource $source MIME message source
*/
function
__construct
(
$source
)
{
$this
->
parse_mime
(
$source
);
}
/**
* Returns message headers
*
* @return array Message headers
*/
public
function
headers
()
{
return
$this
->
headers
;
}
public
function
source
()
{
$headers
=
array
();
// Build the message back
foreach
(
$this
->
headers
as
$header
=>
$header_value
)
{
$headers
[
$header
]
=
$header
.
': '
.
$header_value
;
}
return
trim
(
implode
(
"
\r\n
"
,
$headers
))
.
"
\r\n\r\n
"
.
ltrim
(
$this
->
body
);
// @TODO: work with file streams
}
/**
* Appends text at the end of the message body
*
* @todo: HTML support
*
* @param string $text Text to append
* @param string $charset Text charset
*/
public
function
append
(
$text
,
$charset
=
null
)
{
if
(
$this
->
ctype
==
'text/plain'
)
{
// decode body
$body
=
$this
->
decode
(
$this
->
body
,
$this
->
headers
[
'Content-Transfer-Encoding'
]);
$body
=
rcube_charset
::
convert
(
$body
,
$this
->
ctype_params
[
'charset'
],
$charset
);
// append text
$body
.=
$text
;
// encode and save
$body
=
rcube_charset
::
convert
(
$body
,
$charset
,
$this
->
ctype_params
[
'charset'
]);
$this
->
body
=
$this
->
encode
(
$body
,
$this
->
headers
[
'Content-Transfer-Encoding'
]);
}
}
/**
* Adds attachment to the message
*
* @param string $body Attachment body (not encoded)
* @param string $params Attachment parameters (Mail_mimePart format)
*/
public
function
add_attachment
(
$body
,
$params
=
array
())
{
// convert the message into multipart/mixed
if
(
$this
->
ctype
!=
'multipart/mixed'
)
{
$boundary
=
'_'
.
md5
(
rand
()
.
microtime
());
$this
->
body
=
"--$boundary
\r\n
"
.
"Content-Type: "
.
$this
->
headers
[
'Content-Type'
].
"
\r\n
"
.
"Content-Transfer-Encoding: "
.
$this
->
headers
[
'Content-Transfer-Encoding'
].
"
\r\n
"
.
"
\r\n
"
.
trim
(
$this
->
body
)
.
"
\r\n
"
.
"--$boundary
\r\n
"
;
$this
->
ctype
=
'multipart/mixed'
;
$this
->
ctype_params
=
array
(
'boundary'
=>
$boundary
);
unset
(
$this
->
headers
[
'Content-Transfer-Encoding'
]);
$this
->
save_content_type
(
$this
->
ctype
,
$this
->
ctype_params
);
}
// make sure MIME-Version header is set, it's required by some servers
if
(
empty
(
$this
->
headers
[
'MIME-Version'
]))
{
$this
->
headers
[
'MIME-Version'
]
=
'1.0'
;
}
$boundary
=
$this
->
ctype_params
[
'boundary'
];
$part
=
new
Mail_mimePart
(
$body
,
$params
);
$body
=
$part
->
encode
();
foreach
(
$body
[
'headers'
]
as
$name
=>
$value
)
{
$body
[
'headers'
][
$name
]
=
$name
.
': '
.
$value
;
}
$this
->
body
=
rtrim
(
$this
->
body
);
$this
->
body
=
preg_replace
(
'/--$/'
,
''
,
$this
->
body
);
// add the attachment to the end of the message
$this
->
body
.=
"
\r\n
"
.
implode
(
"
\r\n
"
,
$body
[
'headers'
])
.
"
\r\n\r\n
"
.
$body
[
'body'
]
.
"
\r\n
--$boundary--
\r\n
"
;
}
/**
* Sets the value of specified message header
*
* @param string $name Header name
* @param string $value Header value
*/
public
function
set_header
(
$name
,
$value
)
{
$name
=
self
::
normalize_header_name
(
$name
);
if
(
$name
!=
'Content-Type'
)
{
$this
->
headers
[
$name
]
=
$value
;
}
}
/**
* Send the given message using the configured method.
*
* @param array $smtp_error SMTP error array (reference)
* @param array $smtp_opts SMTP options (e.g. DSN request)
*
* @return boolean Send status.
*/
public
function
send
(&
$smtp_error
=
null
,
$smtp_opts
=
null
)
{
$rcube
=
rcube
::
get_instance
();
$headers
=
$this
->
headers
;
$mailto
=
$headers
[
'To'
];
$headers
[
'User-Agent'
]
.=
$rcube
->
app_name
.
' '
.
kolab_sync
::
VERSION
;
if
(
$agent
=
$rcube
->
config
->
get
(
'useragent'
))
{
$headers
[
'User-Agent'
]
.=
'/'
.
$agent
;
}
if
(
empty
(
$headers
[
'From'
]))
{
$headers
[
'From'
]
=
$this
->
get_identity
();
}
// make sure there's sender name in From:
else
if
(
$rcube
->
config
->
get
(
'activesync_fix_from'
)
&&
preg_match
(
'/^<?((
\S
+|("[^"]+"))@
\S
+)>?$/'
,
trim
(
$headers
[
'From'
]),
$m
)
)
{
$identities
=
kolab_sync
::
get_instance
()->
user
->
list_identities
();
$email
=
$m
[
1
];
foreach
((
array
)
$identities
as
$ident
)
{
if
(
$ident
[
'email'
]
==
$email
)
{
if
(
$ident
[
'name'
])
{
$headers
[
'From'
]
=
format_email_recipient
(
$email
,
$ident
[
'name'
]);
}
break
;
}
}
}
if
(
empty
(
$headers
[
'Message-ID'
]))
{
$headers
[
'Message-ID'
]
=
$rcube
->
gen_message_id
();
}
// remove empty headers
$headers
=
array_filter
(
$headers
);
$smtp_headers
=
$headers
;
// generate list of recipients
$recipients
=
array
();
if
(!
empty
(
$headers
[
'To'
]))
$recipients
[]
=
$headers
[
'To'
];
if
(!
empty
(
$headers
[
'Cc'
]))
$recipients
[]
=
$headers
[
'Cc'
];
if
(!
empty
(
$headers
[
'Bcc'
]))
$recipients
[]
=
$headers
[
'Bcc'
];
if
(
empty
(
$headers
[
'To'
])
&&
empty
(
$headers
[
'Cc'
]))
{
$headers
[
'To'
]
=
'undisclosed-recipients:;'
;
}
// remove Bcc header
unset
(
$smtp_headers
[
'Bcc'
]);
// send message
if
(!
is_object
(
$rcube
->
smtp
))
{
$rcube
->
smtp_init
(
true
);
}
$sent
=
$rcube
->
smtp
->
send_mail
(
$headers
[
'From'
],
$recipients
,
$smtp_headers
,
$this
->
body
,
$smtp_opts
);
$smtp_response
=
$rcube
->
smtp
->
get_response
();
$smtp_error
=
$rcube
->
smtp
->
get_error
();
// log error
if
(!
$sent
)
{
rcube
::
raise_error
(
array
(
'code'
=>
800
,
'type'
=>
'smtp'
,
'line'
=>
__LINE__
,
'file'
=>
__FILE__
,
'message'
=>
"SMTP error: "
.
join
(
"
\n
"
,
$smtp_response
)),
true
,
false
);
}
if
(
$sent
)
{
$rcube
->
plugins
->
exec_hook
(
'message_sent'
,
array
(
'headers'
=>
$headers
,
'body'
=>
$this
->
body
));
// remove MDN headers after sending
unset
(
$headers
[
'Return-Receipt-To'
],
$headers
[
'Disposition-Notification-To'
]);
if
(
$rcube
->
config
->
get
(
'smtp_log'
))
{
// get all recipient addresses
$mailto
=
rcube_mime
::
decode_address_list
(
implode
(
','
,
$recipients
),
null
,
false
,
null
,
true
);
rcube
::
write_log
(
'sendmail'
,
sprintf
(
"User %s [%s]; Message %s for %s; %s"
,
$rcube
->
get_user_name
(),
rcube_utils
::
remote_addr
(),
$headers
[
'Message-ID'
],
implode
(
', '
,
$mailto
),
!
empty
(
$smtp_response
)
?
implode
(
'; '
,
$smtp_response
)
:
''
)
);
}
}
$this
->
headers
=
$headers
;
return
$sent
;
}
/**
* Parses the message source and fixes 8bit data for ActiveSync.
* This way any not UTF8 characters will be encoded before
* sending to the device.
*
* @param string $message Message source
*
* @return string Fixed message source
*/
public
static
function
recode_message
(
$message
)
{
// @TODO: work with stream, to workaround memory issues with big messages
if
(
is_resource
(
$message
))
{
$message
=
stream_get_contents
(
$message
);
}
list
(
$headers
,
$message
)
=
preg_split
(
'/
\r
?
\n\r
?
\n
/'
,
$message
,
2
,
PREG_SPLIT_NO_EMPTY
);
$hdrs
=
self
::
parse_headers
(
$headers
);
// multipart message
if
(
preg_match
(
'/boundary="?([a-z0-9-
\'\(\)
+_
\,\.\/
:=
\?
]+)"?/i'
,
$hdrs
[
'Content-Type'
],
$matches
))
{
$boundary
=
'--'
.
$matches
[
1
];
$message
=
explode
(
$boundary
,
$message
);
for
(
$x
=
1
,
$parts
=
count
(
$message
)
-
1
;
$x
<
$parts
;
$x
++)
{
$message
[
$x
]
=
"
\r\n
"
.
self
::
recode_message
(
ltrim
(
$message
[
$x
]));
}
return
$headers
.
"
\r\n\r\n
"
.
implode
(
$boundary
,
$message
);
}
// single part
$enc
=
!
empty
(
$hdrs
[
'Content-Transfer-Encoding'
])
?
strtolower
(
$hdrs
[
'Content-Transfer-Encoding'
])
:
null
;
// do nothing if already encoded
if
(
$enc
!=
'quoted-printable'
&&
$enc
!=
'base64'
)
{
// recode body if any non-printable-ascii characters found
if
(
preg_match
(
'/[^
\x
20-
\x
7E
\x
0A
\x
0D
\x
09]/'
,
$message
))
{
$hdrs
[
'Content-Transfer-Encoding'
]
=
'base64'
;
foreach
(
$hdrs
as
$header
=>
$header_value
)
{
$hdrs
[
$header
]
=
$header
.
': '
.
$header_value
;
}
$headers
=
trim
(
implode
(
"
\r\n
"
,
$hdrs
));
$message
=
rtrim
(
chunk_split
(
base64_encode
(
rtrim
(
$message
)),
76
,
"
\r\n
"
))
.
"
\r\n
"
;
}
}
return
$headers
.
"
\r\n\r\n
"
.
$message
;
}
/**
* Creates a fake plain text message source with predefined headers and body
*
* @param string $headers Message headers
* @param string $body Plain text body
*
* @return string Message source
*/
public
static
function
fake_message
(
$headers
,
$body
=
''
)
{
$hdrs
=
self
::
parse_headers
(
$headers
);
$result
=
''
;
$hdrs
[
'Content-Type'
]
=
'text/plain; charset=UTF-8'
;
$hdrs
[
'Content-Transfer-Encoding'
]
=
'quoted-printable'
;
foreach
(
$hdrs
as
$header
=>
$header_value
)
{
$result
.=
$header
.
': '
.
$header_value
.
"
\r\n
"
;
}
return
$result
.
"
\r\n
"
.
self
::
encode
(
$body
,
'quoted-printable'
);
}
/**
* MIME message parser
*
* @param string|resource $message MIME message source
* @param bool $decode_body Enables body decoding
*
* @return array Message headers array and message body
*/
protected
function
parse_mime
(
$message
)
{
// @TODO: work with stream, to workaround memory issues with big messages
if
(
is_resource
(
$message
))
{
$message
=
stream_get_contents
(
$message
);
}
list
(
$headers
,
$message
)
=
preg_split
(
'/
\r
?
\n\r
?
\n
/'
,
$message
,
2
,
PREG_SPLIT_NO_EMPTY
);
$headers
=
self
::
parse_headers
(
$headers
);
// parse Content-Type header
$ctype_parts
=
preg_split
(
'/[; ]+/'
,
$headers
[
'Content-Type'
]);
$this
->
ctype
=
strtolower
(
array_shift
(
$ctype_parts
));
foreach
(
$ctype_parts
as
$part
)
{
if
(
preg_match
(
'/^([a-z-_]+)
\s
*=
\s
*(.+)$/i'
,
trim
(
$part
),
$m
))
{
$this
->
ctype_params
[
strtolower
(
$m
[
1
])]
=
trim
(
$m
[
2
],
'"'
);
}
}
if
(!
empty
(
$headers
[
'Content-Transfer-Encoding'
]))
{
$headers
[
'Content-Transfer-Encoding'
]
=
strtolower
(
$headers
[
'Content-Transfer-Encoding'
]);
}
$this
->
headers
=
$headers
;
$this
->
body
=
$message
;
}
/**
* Parse message source with headers
*/
protected
static
function
parse_headers
(
$headers
)
{
// Parse headers
$headers
=
str_replace
(
"
\r\n
"
,
"
\n
"
,
$headers
);
$headers
=
explode
(
"
\n
"
,
trim
(
$headers
));
$ln
=
0
;
$lines
=
array
();
foreach
(
$headers
as
$line
)
{
if
(
ord
(
$line
[
0
])
<=
32
)
{
$lines
[
$ln
]
.=
(
empty
(
$lines
[
$ln
])
?
''
:
"
\r\n
"
)
.
$line
;
}
else
{
$lines
[++
$ln
]
=
trim
(
$line
);
}
}
// Unify char-case of header names
$headers
=
array
();
foreach
(
$lines
as
$line
)
{
list
(
$field
,
$string
)
=
explode
(
':'
,
$line
,
2
);
if
(
$field
=
self
::
normalize_header_name
(
$field
))
{
$headers
[
$field
]
=
trim
(
$string
);
}
}
return
$headers
;
}
/**
* Normalize (fix) header names
*/
protected
static
function
normalize_header_name
(
$name
)
{
$headers_map
=
array
(
'subject'
=>
'Subject'
,
'from'
=>
'From'
,
'to'
=>
'To'
,
'cc'
=>
'Cc'
,
'bcc'
=>
'Bcc'
,
'message-id'
=>
'Message-ID'
,
'references'
=>
'References'
,
'content-type'
=>
'Content-Type'
,
'content-transfer-encoding'
=>
'Content-Transfer-Encoding'
,
);
$name_lc
=
strtolower
(
$name
);
return
isset
(
$headers_map
[
$name_lc
])
?
$headers_map
[
$name_lc
]
:
$name
;
}
/**
* Encodes message/part body
*
* @param string $body Message/part body
* @param string $encoding Content encoding
*
* @return string Encoded body
*/
protected
function
encode
(
$body
,
$encoding
)
{
switch
(
$encoding
)
{
case
'base64'
:
$body
=
base64_encode
(
$body
);
$body
=
chunk_split
(
$body
,
76
,
"
\r\n
"
);
break
;
case
'quoted-printable'
:
$body
=
quoted_printable_encode
(
$body
);
break
;
}
return
$body
;
}
/**
* Decodes message/part body
*
* @param string $body Message/part body
* @param string $encoding Content encoding
*
* @return string Decoded body
*/
protected
function
decode
(
$body
,
$encoding
)
{
$body
=
str_replace
(
"
\r\n
"
,
"
\n
"
,
$body
);
switch
(
$encoding
)
{
case
'base64'
:
$body
=
base64_decode
(
$body
);
break
;
case
'quoted-printable'
:
$body
=
quoted_printable_decode
(
$body
);
break
;
}
return
$body
;
}
/**
* Returns email address string from default identity of the current user
*/
protected
function
get_identity
()
{
$user
=
kolab_sync
::
get_instance
()->
user
;
if
(
$identity
=
$user
->
get_identity
())
{
return
format_email_recipient
(
format_email
(
$identity
[
'email'
]),
$identity
[
'name'
]);
}
}
protected
function
save_content_type
(
$ctype
,
$params
=
array
())
{
$this
->
ctype
=
$ctype
;
$this
->
ctype_params
=
$params
;
$this
->
headers
[
'Content-Type'
]
=
$ctype
;
if
(!
empty
(
$params
))
{
foreach
(
$params
as
$name
=>
$value
)
{
$this
->
headers
[
'Content-Type'
]
.=
sprintf
(
'; %s="%s"'
,
$name
,
$value
);
}
}
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Mon, Aug 25, 5:05 PM (19 h, 4 s)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
252289
Default Alt Text
kolab_sync_message.php (16 KB)
Attached To
Mode
R4 syncroton
Attached
Detach File
Event Timeline
Log In to Comment