-
Notifications
You must be signed in to change notification settings - Fork 360
Expand file tree
/
Copy pathsms.php
More file actions
127 lines (110 loc) · 4.2 KB
/
Copy pathsms.php
File metadata and controls
127 lines (110 loc) · 4.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
<?php
/**
* Validates sms (for text messaging).
*
* The relevant specification for this protocol is RFC 5724.
* This class normalizes SMS numbers so that they only include
* digits, optionally with a leading plus for international numbers.
*
* According to RFC 5724, SMS URIs support the 'body' parameter
* using the format: sms:number?body=message
* However, the format: sms:number&body=message is commonly used on
* the web, so it is also supported here.
*/
class HTMLPurifier_URIScheme_sms extends HTMLPurifier_URIScheme
{
/**
* @type bool
*/
public $browsable = false;
/**
* @type bool
*/
public $may_omit_host = true;
/**
* @param HTMLPurifier_URI $uri
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return bool
*/
public function doValidate(&$uri, $config, $context)
{
$uri->userinfo = null;
$uri->host = null;
$uri->port = null;
// Extract phone number and parameters from path and query
$phone_number = $uri->path;
$body_content = null;
// Check if path contains ¶m= syntax (non-standard but common)
if (strpos($phone_number, '&') !== false) {
// Split by & to get phone number and parameters
$parts = explode('&', $phone_number);
$phone_number = array_shift($parts); // First part is the phone number
// Parse parameters from path
foreach ($parts as $param) {
if (strpos($param, '=') !== false) {
list($param_name, $param_value) = explode('=', $param, 2);
if ($param_name === 'body') {
$body_content = $param_value;
}
// Other parameters (subject, invalid, etc.) are ignored/stripped
}
}
}
// Also check query string for body parameter (standard ?body= syntax)
// Query takes precedence if present (parser converts &body= to ?body=)
// The query may contain multiple parameters like "body=Hello&subject=Test"
if (!is_null($uri->query)) {
// Parse query parameters
$query_parts = explode('&', $uri->query);
foreach ($query_parts as $query_param) {
if (strpos($query_param, '=') !== false) {
list($param_name, $param_value) = explode('=', $query_param, 2);
if ($param_name === 'body') {
$body_content = $param_value;
break; // Only take the first body parameter
}
}
}
}
// Clean the phone number part
$phone_number = preg_replace(
'/(?!^\+)[^\d]/',
'',
rawurldecode($phone_number)
);
// Sanitize the body content if present
if ($body_content !== null) {
$body_content = $this->sanitizeBody($body_content);
}
// Reconstruct the path with &body= syntax (non-standard but common format)
if ($body_content !== null) {
// Always include &body= even if empty (per test expectations)
$uri->path = $phone_number . '&body=' . $body_content;
} else {
$uri->path = $phone_number;
}
// Clear query since we're using &body= in path format
$uri->query = null;
return true;
}
/**
* Sanitizes SMS body content
* @param string $body
* @return string
*/
private function sanitizeBody($body)
{
// Decode URL encoding first so encoded payloads are caught
$decoded = rawurldecode($body);
// Angle brackets are the primary HTML injection vector — reject the
// entire body if they appear rather than trying to strip them partially
if (strpos($decoded, '<') !== false || strpos($decoded, '>') !== false) {
return '';
}
// Strip quote characters that could break HTML attribute context
$sanitized = preg_replace('/[\'"]/', '', $decoded);
// Re-encode so the value is safe for embedding in a URL attribute
return rawurlencode($sanitized);
}
}