1: <?php defined('_JOOS_CORE') or exit();
2:
3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15:
16: class joosInputFilter
17: {
18: 19: 20:
21: private static $instance;
22: protected $tagsArray;
23: protected $attrArray;
24: protected $tagsMethod;
25: protected $attrMethod;
26: protected $xssAuto;
27: protected $tagBlacklist = array('applet', 'body', 'bgsound', 'base', 'basefont', 'embed', 'frame', 'frameset', 'head', 'html', 'id', 'iframe', 'ilayer', 'layer', 'link', 'meta', 'name', 'object', 'script', 'style', 'title', 'xml');
28: protected $attrBlacklist = array('action', 'background', 'codebase', 'dynsrc', 'lowsrc');
29:
30: private function __construct($tagsArray = array(), $attrArray = array(), $tagsMethod = 0, $attrMethod = 0, $xssAuto = 1)
31: {
32: $tagsArray = array_map('strtolower', (array) $tagsArray);
33: $attrArray = array_map('strtolower', (array) $attrArray);
34: $this->tagsArray = (array) $tagsArray;
35: $this->attrArray = (array) $attrArray;
36: $this->tagsMethod = $tagsMethod;
37: $this->attrMethod = $attrMethod;
38: $this->xssAuto = $xssAuto;
39: }
40:
41: public static function instance($tagsArray = array(), $attrArray = array(), $tagsMethod = 0, $attrMethod = 0, $xssAuto = 1)
42: {
43: !JDEBUG ? : joosDebug::inc('joosInputFilter::instance');
44:
45: if (self::$instance === null) {
46: self::$instance = new self($tagsArray, $attrArray, $tagsMethod, $attrMethod, $xssAuto);
47: }
48:
49: return self::$instance;
50: }
51:
52: private function __clone()
53: {
54: }
55:
56: public function process($source)
57: {
58: if (is_array($source)) {
59: foreach ($source as $key => $value) {
60: if (is_string($value)) {
61: $source[$key] = $this->remove($this->decode($value));
62: }
63: }
64:
65: return $source;
66: } else {
67: if (is_string($source) && !empty($source)) {
68: return $this->remove($this->decode($source));
69: } else {
70: return $source;
71: }
72: }
73: }
74:
75: protected function remove($source)
76: {
77:
78: while ($source != $this->filterTags($source)) {
79: $source = $this->filterTags($source);
80:
81: }
82:
83: return $source;
84: }
85:
86: protected function filterTags($source)
87: {
88: $preTag = null;
89: $postTag = $source;
90: $tagOpen_start = strpos($source, '<');
91: while ($tagOpen_start !== false) {
92: $preTag .= substr($postTag, 0, $tagOpen_start);
93: $postTag = substr($postTag, $tagOpen_start);
94: $fromTagOpen = substr($postTag, 1);
95: $tagOpen_end = strpos($fromTagOpen, '>');
96: if ($tagOpen_end === false) {
97: $postTag = substr($postTag, $tagOpen_start + 1);
98: $tagOpen_start = strpos($postTag, '<');
99: continue;
100: }
101: $tagOpen_nested = strpos($fromTagOpen, '<');
102:
103: if (($tagOpen_nested !== false) && ($tagOpen_nested < $tagOpen_end)) {
104: $preTag .= substr($postTag, 0, ($tagOpen_nested + 1));
105: $postTag = substr($postTag, ($tagOpen_nested + 1));
106: $tagOpen_start = strpos($postTag, '<');
107: continue;
108: }
109:
110: $currentTag = substr($fromTagOpen, 0, $tagOpen_end);
111: $tagLength = strlen($currentTag);
112: $tagLeft = $currentTag;
113: $attrSet = array();
114: $currentSpace = strpos($tagLeft, ' ');
115: if (substr($currentTag, 0, 1) == "/") {
116: $isCloseTag = true;
117: list($tagName) = explode(' ', $currentTag);
118: $tagName = substr($tagName, 1);
119: } else {
120: $isCloseTag = false;
121: list($tagName) = explode(' ', $currentTag);
122: }
123: if ((!preg_match("/^[a-z][a-z0-9]*$/iu", $tagName)) || (!$tagName) || ((in_array(strtolower($tagName), $this->tagBlacklist)) && ($this->xssAuto))) {
124: $postTag = substr($postTag, ($tagLength + 2));
125: $tagOpen_start = strpos($postTag, '<');
126: continue;
127: }
128: while ($currentSpace !== false) {
129: $fromSpace = substr($tagLeft, ($currentSpace + 1));
130: $nextSpace = strpos($fromSpace, ' ');
131: $openQuotes = strpos($fromSpace, '"');
132: $closeQuotes = strpos(substr($fromSpace, ($openQuotes + 1)), '"') + $openQuotes + 1;
133: if (strpos($fromSpace, '=') !== false) {
134: if (($openQuotes !== false) && (strpos(substr($fromSpace, ($openQuotes + 1)), '"') !== false)) {
135: $attr = substr($fromSpace, 0, ($closeQuotes + 1));
136: } else {
137: $attr = substr($fromSpace, 0, $nextSpace);
138: }
139: } else {
140: $attr = substr($fromSpace, 0, $nextSpace);
141: }
142: if (!$attr) {
143: $attr = $fromSpace;
144: }
145: $attrSet[] = $attr;
146: $tagLeft = substr($fromSpace, strlen($attr));
147: $currentSpace = strpos($tagLeft, ' ');
148: }
149: $tagFound = in_array(strtolower($tagName), $this->tagsArray);
150: if ((!$tagFound && $this->tagsMethod) || ($tagFound && !$this->tagsMethod)) {
151: if (!$isCloseTag) {
152: $attrSet = $this->filterAttr($attrSet);
153: $preTag .= '<' . $tagName;
154: for ($i = 0; $i < count($attrSet); $i++) {
155: $preTag .= ' ' . $attrSet[$i];
156: }
157: if (strpos($fromTagOpen, "</" . $tagName)) {
158: $preTag .= '>';
159: } else {
160: $preTag .= ' />';
161: }
162: } else {
163: $preTag .= '</' . $tagName . '>';
164: }
165: }
166: $postTag = substr($postTag, ($tagLength + 2));
167: $tagOpen_start = strpos($postTag, '<');
168: }
169: if ($postTag != '<') {
170: $preTag .= $postTag;
171: }
172:
173: return $preTag;
174: }
175:
176: protected function filterAttr($attrSet)
177: {
178: $newSet = array();
179: for ($i = 0; $i < count($attrSet); $i++) {
180: if (!$attrSet[$i]) {
181: continue;
182: }
183: $attrSubSet = explode('=', trim($attrSet[$i]), 2);
184: list($attrSubSet[0]) = explode(' ', $attrSubSet[0]);
185:
186: if ((!eregi("^[a-z]*$", $attrSubSet[0])) || (($this->xssAuto) && ((in_array(strtolower($attrSubSet[0]), $this->attrBlacklist)) || (substr(strtolower($attrSubSet[0]), 0, 2) == 'on')))) {
187: continue;
188: }
189: if ($attrSubSet[1]) {
190: $attrSubSet[1] = str_replace('&#', '', $attrSubSet[1]);
191: $attrSubSet[1] = preg_replace('/\s+/', '', $attrSubSet[1]);
192: $attrSubSet[1] = str_replace('"', '', $attrSubSet[1]);
193: if ((substr($attrSubSet[1], 0, 1) == "'") && (substr($attrSubSet[1], (strlen($attrSubSet[1]) - 1), 1) == "'")) {
194: $attrSubSet[1] = substr($attrSubSet[1], 1, (strlen($attrSubSet[1]) - 2));
195: }
196: $attrSubSet[1] = stripslashes($attrSubSet[1]);
197: }
198: if (self::badAttributeValue($attrSubSet)) {
199: continue;
200: }
201: $attrFound = in_array(strtolower($attrSubSet[0]), $this->attrArray);
202: if ((!$attrFound && $this->attrMethod) || ($attrFound && !$this->attrMethod)) {
203: if ($attrSubSet[1]) {
204: $newSet[] = $attrSubSet[0] . '="' . $attrSubSet[1] . '"';
205: } elseif ($attrSubSet[1] == "0") {
206: $newSet[] = $attrSubSet[0] . '="0"';
207: } else {
208: $newSet[] = $attrSubSet[0] . '="' . $attrSubSet[0] . '"';
209: }
210: }
211: }
212:
213: return $newSet;
214: }
215:
216: public function badAttributeValue($attrSubSet)
217: {
218: $attrSubSet[0] = strtolower($attrSubSet[0]);
219: $attrSubSet[1] = strtolower($attrSubSet[1]);
220:
221: return (((strpos($attrSubSet[1], 'expression') !== false) && ($attrSubSet[0]) == 'style') || (strpos($attrSubSet[1], 'javascript:') !== false) || (strpos($attrSubSet[1], 'behaviour:') !== false) || (strpos($attrSubSet[1], 'vbscript:') !== false) || (strpos($attrSubSet[1], 'mocha:') !== false) || (strpos($attrSubSet[1], 'livescript:') !== false));
222: }
223:
224: protected function decode($source)
225: {
226: $source = html_entity_decode($source, ENT_QUOTES, "UTF-8");
227: $source = preg_replace('/&#(\d+);/me', "chr(\\1)", $source);
228: $source = preg_replace('/&#x([a-f0-9]+);/mei', "chr(0x\\1)", $source);
229:
230: return $source;
231: }
232:
233: public function safeSQL($source)
234: {
235: if (is_array($source)) {
236: foreach ($source as $key => $value) {
237: if (is_string($value)) {
238: $source[$key] = $this->quoteSmart($this->decode($value));
239: }
240: }
241:
242: return $source;
243: } elseif (is_string($source)) {
244: if (is_string($source)) {
245: return $this->quoteSmart($this->decode($source));
246: }
247: } else {
248: return $source;
249: }
250:
251: return 'error';
252: }
253:
254: protected function quoteSmart($source)
255: {
256: $source = $this->escapeString($source);
257:
258: return $source;
259: }
260:
261: protected function escapeString($string)
262: {
263: $string = mysql_real_escape_string($string);
264:
265: return $string;
266: }
267:
268: }
269: