1: <?php
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: require_once "Auth/OpenID.php";
86:
87: class Auth_OpenID_Parse {
88:
89: 90: 91:
92: var $_re_flags = "si";
93:
94: 95: 96:
97: var $_removed_re =
98: "<!--.*?-->|<!\[CDATA\[.*?\]\]>|<script\b(?!:)[^>]*>.*?<\/script>";
99:
100: 101: 102: 103:
104: var $_tag_expr = "<%s\b(?!:)([^>]*?)(?:\/>|>(.*)(?:<\/?%s\s*>|\Z))";
105:
106: var $_attr_find = '\b(\w+)=("[^"]*"|\'[^\']*\'|[^\'"\s\/<>]+)';
107:
108: var $_open_tag_expr = "<%s\b";
109: var $_close_tag_expr = "<((\/%s\b)|(%s[^>\/]*\/))>";
110:
111: function Auth_OpenID_Parse()
112: {
113: $this->_link_find = sprintf("/<link\b(?!:)([^>]*)(?!<)>/%s",
114: $this->_re_flags);
115:
116: $this->_entity_replacements = array(
117: 'amp' => '&',
118: 'lt' => '<',
119: 'gt' => '>',
120: 'quot' => '"'
121: );
122:
123: $this->_attr_find = sprintf("/%s/%s",
124: $this->_attr_find,
125: $this->_re_flags);
126:
127: $this->_removed_re = sprintf("/%s/%s",
128: $this->_removed_re,
129: $this->_re_flags);
130:
131: $this->_ent_replace =
132: sprintf("&(%s);", implode("|",
133: $this->_entity_replacements));
134: }
135:
136: 137: 138: 139:
140: function tagMatcher($tag_name, $close_tags = null)
141: {
142: $expr = $this->_tag_expr;
143:
144: if ($close_tags) {
145: $options = implode("|", array_merge(array($tag_name), $close_tags));
146: $closer = sprintf("(?:%s)", $options);
147: } else {
148: $closer = $tag_name;
149: }
150:
151: $expr = sprintf($expr, $tag_name, $closer);
152: return sprintf("/%s/%s", $expr, $this->_re_flags);
153: }
154:
155: function openTag($tag_name)
156: {
157: $expr = sprintf($this->_open_tag_expr, $tag_name);
158: return sprintf("/%s/%s", $expr, $this->_re_flags);
159: }
160:
161: function closeTag($tag_name)
162: {
163: $expr = sprintf($this->_close_tag_expr, $tag_name, $tag_name);
164: return sprintf("/%s/%s", $expr, $this->_re_flags);
165: }
166:
167: function htmlBegin($s)
168: {
169: $matches = array();
170: $result = preg_match($this->openTag('html'), $s,
171: $matches, PREG_OFFSET_CAPTURE);
172: if ($result === false || !$matches) {
173: return false;
174: }
175:
176: return $matches[0][1];
177: }
178:
179: function htmlEnd($s)
180: {
181: $matches = array();
182: $result = preg_match($this->closeTag('html'), $s,
183: $matches, PREG_OFFSET_CAPTURE);
184: if ($result === false || !$matches) {
185: return false;
186: }
187:
188: return $matches[count($matches) - 1][1];
189: }
190:
191: function headFind()
192: {
193: return $this->tagMatcher('head', array('body', 'html'));
194: }
195:
196: function replaceEntities($str)
197: {
198: foreach ($this->_entity_replacements as $old => $new) {
199: $str = preg_replace(sprintf("/&%s;/", $old), $new, $str);
200: }
201: return $str;
202: }
203:
204: function removeQuotes($str)
205: {
206: $matches = array();
207: $double = '/^"(.*)"$/';
208: $single = "/^\'(.*)\'$/";
209:
210: if (preg_match($double, $str, $matches)) {
211: return $matches[1];
212: } else if (preg_match($single, $str, $matches)) {
213: return $matches[1];
214: } else {
215: return $str;
216: }
217: }
218:
219: function match($regexp, $text, &$match)
220: {
221: if (!is_callable('mb_ereg_search_init')) {
222: return preg_match($regexp, $text, $match);
223: }
224:
225: $regexp = substr($regexp, 1, strlen($regexp) - 2 - strlen($this->_re_flags));
226: mb_ereg_search_init($text);
227: if (!mb_ereg_search($regexp)) {
228: return false;
229: }
230: $match = mb_ereg_search_getregs();
231: return true;
232: }
233:
234: 235: 236: 237: 238: 239: 240: 241: 242: 243: 244: 245: 246:
247: function parseLinkAttrs($html)
248: {
249: $stripped = preg_replace($this->_removed_re,
250: "",
251: $html);
252:
253: $html_begin = $this->htmlBegin($stripped);
254: $html_end = $this->htmlEnd($stripped);
255:
256: if ($html_begin === false) {
257: return array();
258: }
259:
260: if ($html_end === false) {
261: $html_end = strlen($stripped);
262: }
263:
264: $stripped = substr($stripped, $html_begin,
265: $html_end - $html_begin);
266:
267:
268: $old_btlimit = ini_set( 'pcre.backtrack_limit', -1 );
269:
270:
271: $head_re = $this->headFind();
272: $head_match = array();
273: if (!$this->match($head_re, $stripped, $head_match)) {
274: ini_set( 'pcre.backtrack_limit', $old_btlimit );
275: return array();
276: }
277:
278: $link_data = array();
279: $link_matches = array();
280:
281: if (!preg_match_all($this->_link_find, $head_match[0],
282: $link_matches)) {
283: ini_set( 'pcre.backtrack_limit', $old_btlimit );
284: return array();
285: }
286:
287: foreach ($link_matches[0] as $link) {
288: $attr_matches = array();
289: preg_match_all($this->_attr_find, $link, $attr_matches);
290: $link_attrs = array();
291: foreach ($attr_matches[0] as $index => $full_match) {
292: $name = $attr_matches[1][$index];
293: $value = $this->replaceEntities(
294: $this->removeQuotes($attr_matches[2][$index]));
295:
296: $link_attrs[strtolower($name)] = $value;
297: }
298: $link_data[] = $link_attrs;
299: }
300:
301: ini_set( 'pcre.backtrack_limit', $old_btlimit );
302: return $link_data;
303: }
304:
305: function relMatches($rel_attr, $target_rel)
306: {
307:
308: $rels = preg_split("/\s+/", trim($rel_attr));
309: foreach ($rels as $rel) {
310: $rel = strtolower($rel);
311: if ($rel == $target_rel) {
312: return 1;
313: }
314: }
315:
316: return 0;
317: }
318:
319: function linkHasRel($link_attrs, $target_rel)
320: {
321:
322: $rel_attr = Auth_OpeniD::arrayGet($link_attrs, 'rel', null);
323: return ($rel_attr && $this->relMatches($rel_attr,
324: $target_rel));
325: }
326:
327: function findLinksRel($link_attrs_list, $target_rel)
328: {
329:
330:
331: $result = array();
332: foreach ($link_attrs_list as $attr) {
333: if ($this->linkHasRel($attr, $target_rel)) {
334: $result[] = $attr;
335: }
336: }
337:
338: return $result;
339: }
340:
341: function findFirstHref($link_attrs_list, $target_rel)
342: {
343:
344:
345: $matches = $this->findLinksRel($link_attrs_list,
346: $target_rel);
347: if (!$matches) {
348: return null;
349: }
350: $first = $matches[0];
351: return Auth_OpenID::arrayGet($first, 'href', null);
352: }
353: }
354:
355: function Auth_OpenID_legacy_discover($html_text, $server_rel,
356: $delegate_rel)
357: {
358: $p = new Auth_OpenID_Parse();
359:
360: $link_attrs = $p->parseLinkAttrs($html_text);
361:
362: $server_url = $p->findFirstHref($link_attrs,
363: $server_rel);
364:
365: if ($server_url === null) {
366: return false;
367: } else {
368: $delegate_url = $p->findFirstHref($link_attrs,
369: $delegate_rel);
370: return array($delegate_url, $server_url);
371: }
372: }
373:
374: