-rw-r--r-- | lib/openid_service_resolver.cc | 18 |
1 files changed, 11 insertions, 7 deletions
diff --git a/lib/openid_service_resolver.cc b/lib/openid_service_resolver.cc index 5f82955..505e5b2 100644 --- a/lib/openid_service_resolver.cc +++ b/lib/openid_service_resolver.cc | |||
@@ -55,240 +55,244 @@ namespace opkele { | |||
55 | t.skip_tags = false; | 55 | t.skip_tags = false; |
56 | else if(is_qelement(n,NSURI_XRD "\tService") | 56 | else if(is_qelement(n,NSURI_XRD "\tService") |
57 | || is_qelement(n,NSURI_XRD "\tType") | 57 | || is_qelement(n,NSURI_XRD "\tType") |
58 | || is_qelement(n,NSURI_XRD "\tURI") | 58 | || is_qelement(n,NSURI_XRD "\tURI") |
59 | || is_qelement(n,NSURI_OPENID10 "\tDelegate") | 59 | || is_qelement(n,NSURI_OPENID10 "\tDelegate") |
60 | || is_qelement(n,NSURI_XRD "\tCanonicalID") ) | 60 | || is_qelement(n,NSURI_XRD "\tCanonicalID") ) |
61 | t.skip_tags = t.skip_text = false; | 61 | t.skip_tags = t.skip_text = false; |
62 | else if(is_element(n,"body")) | 62 | else if(is_element(n,"body")) |
63 | state = state_stopping_body; | 63 | state = state_stopping_body; |
64 | } | 64 | } |
65 | void openid_service_resolver_t::end_element(const XML_Char *n) { | 65 | void openid_service_resolver_t::end_element(const XML_Char *n) { |
66 | if(state!=state_parse) return; | 66 | if(state!=state_parse) return; |
67 | assert(tree.top().element == n); | 67 | assert(tree.top().element == n); |
68 | pop_tag(); | 68 | pop_tag(); |
69 | } | 69 | } |
70 | void openid_service_resolver_t::character_data(const XML_Char *s,int l) { | 70 | void openid_service_resolver_t::character_data(const XML_Char *s,int l) { |
71 | if(state!=state_parse) return; | 71 | if(state!=state_parse) return; |
72 | if( !( tree.empty() || tree.top().skip_text ) ) | 72 | if( !( tree.empty() || tree.top().skip_text ) ) |
73 | tree.top().content.append(s,l); | 73 | tree.top().content.append(s,l); |
74 | } | 74 | } |
75 | 75 | ||
76 | static void copy_trim_whitespace(string& to,const string& from) { | 76 | static void copy_trim_whitespace(string& to,const string& from) { |
77 | string::size_type ns0 = from.find_first_not_of(whitespace); | 77 | string::size_type ns0 = from.find_first_not_of(whitespace); |
78 | if(ns0==string::npos) { | 78 | if(ns0==string::npos) { |
79 | to.clear(); return; | 79 | to.clear(); return; |
80 | } | 80 | } |
81 | string::size_type ns1 = from.find_last_not_of(whitespace); | 81 | string::size_type ns1 = from.find_last_not_of(whitespace); |
82 | assert(ns1!=string::npos); | 82 | assert(ns1!=string::npos); |
83 | to.assign(from,ns0,ns1-ns0+1); | 83 | to.assign(from,ns0,ns1-ns0+1); |
84 | } | 84 | } |
85 | 85 | ||
86 | void openid_service_resolver_t::pop_tag() { | 86 | void openid_service_resolver_t::pop_tag() { |
87 | assert(!tree.empty()); | 87 | assert(!tree.empty()); |
88 | parser_node_t& t = tree.top(); | 88 | parser_node_t& t = tree.top(); |
89 | if( is_element(t,"meta") | 89 | if( is_element(t,"meta") |
90 | && !strcasecmp(t.attrs["http-equiv"].c_str(),LOCATION_HEADER) ) { | 90 | && !strcasecmp(t.attrs["http-equiv"].c_str(),LOCATION_HEADER) ) { |
91 | xrds_location = t.attrs["content"]; | 91 | xrds_location = t.attrs["content"]; |
92 | }else if( is_element(t,"link") ) { | 92 | }else if( is_element(t,"link") ) { |
93 | parser_node_t::attrs_t::const_iterator ir = t.attrs.find("rel"); | 93 | parser_node_t::attrs_t::const_iterator ir = t.attrs.find("rel"); |
94 | if(ir!=t.attrs.end()) { | 94 | if(ir!=t.attrs.end()) { |
95 | const string& rels = ir->second; | 95 | const string& rels = ir->second; |
96 | for(string::size_type ns = rels.find_first_not_of(whitespace); | 96 | for(string::size_type ns = rels.find_first_not_of(whitespace); |
97 | ns!=string::npos; | 97 | ns!=string::npos; |
98 | ns=rels.find_first_not_of(whitespace,ns)) { | 98 | ns=rels.find_first_not_of(whitespace,ns)) { |
99 | string::size_type s = rels.find_first_of(whitespace,ns); | 99 | string::size_type s = rels.find_first_of(whitespace,ns); |
100 | string rel; | 100 | string rel; |
101 | if(s==string::npos) { | 101 | if(s==string::npos) { |
102 | rel.assign(rels,ns,string::npos); | 102 | rel.assign(rels,ns,string::npos); |
103 | ns = string::npos; | 103 | ns = string::npos; |
104 | }else{ | 104 | }else{ |
105 | rel.assign(rels,ns,s-ns); | 105 | rel.assign(rels,ns,s-ns); |
106 | ns = s; | 106 | ns = s; |
107 | } | 107 | } |
108 | if(rel=="openid.server") | 108 | if(rel=="openid.server") |
109 | copy_trim_whitespace(html_SEP.xrd_URI,t.attrs["href"]); | 109 | copy_trim_whitespace(html_SEP.xrd_URI,t.attrs["href"]); |
110 | else if(rel=="openid.delegate") | 110 | else if(rel=="openid.delegate") |
111 | copy_trim_whitespace(html_SEP.openid_Delegate,t.attrs["href"]); | 111 | copy_trim_whitespace(html_SEP.openid_Delegate,t.attrs["href"]); |
112 | } | 112 | } |
113 | } | 113 | } |
114 | }else if( is_element(t,"head") ) | 114 | }else if( is_element(t,"head") ) |
115 | state = state_stopping_head; | 115 | state = state_stopping_head; |
116 | else if( is_qelement(t,NSURI_XRD "\tXRD")) { | 116 | else if( is_qelement(t,NSURI_XRD "\tXRD")) { |
117 | if( !( | 117 | if( !( |
118 | ( | 118 | ( |
119 | xri_mode | 119 | xri_mode |
120 | && t.auth_info.canonical_id.empty() | 120 | && t.auth_info.canonical_id.empty() |
121 | ) || | 121 | ) || |
122 | t.auth_info.auth_SEP.xrd_Type.empty() | 122 | t.auth_info.auth_SEP.xrd_Type.empty() |
123 | ) ) | 123 | ) ) |
124 | auth_info = t.auth_info; | 124 | auth_info = t.auth_info; |
125 | }else if( tree.size()>1 ) { | 125 | }else if( tree.size()>1 ) { |
126 | parser_node_t& p = tree.parent(); | 126 | parser_node_t& p = tree.parent(); |
127 | if( is_qelement(p,NSURI_XRD "\tService") ) { | 127 | if( is_qelement(p,NSURI_XRD "\tService") ) { |
128 | if( is_qelement(t,NSURI_XRD "\tType") ) { | 128 | if( is_qelement(t,NSURI_XRD "\tType") ) { |
129 | if(t.content==STURI_OPENID10) { | 129 | if(t.content==STURI_OPENID10) { |
130 | string tmp; copy_trim_whitespace(tmp,t.content); | 130 | string tmp; copy_trim_whitespace(tmp,t.content); |
131 | p.auth_info.auth_SEP.xrd_Type.insert(tmp); | 131 | p.auth_info.auth_SEP.xrd_Type.insert(tmp); |
132 | } | 132 | } |
133 | }else if( is_qelement(t,NSURI_XRD "\tURI") ) | 133 | }else if( is_qelement(t,NSURI_XRD "\tURI") ) |
134 | copy_trim_whitespace(p.auth_info.auth_SEP.xrd_URI,t.content); | 134 | copy_trim_whitespace(p.auth_info.auth_SEP.xrd_URI,t.content); |
135 | else if( is_qelement(t,NSURI_OPENID10 "\tDelegate") ) | 135 | else if( is_qelement(t,NSURI_OPENID10 "\tDelegate") ) |
136 | copy_trim_whitespace(p.auth_info.auth_SEP.openid_Delegate,t.content); | 136 | copy_trim_whitespace(p.auth_info.auth_SEP.openid_Delegate,t.content); |
137 | }else if( is_qelement(p,NSURI_XRD "\tXRD") ) { | 137 | }else if( is_qelement(p,NSURI_XRD "\tXRD") ) { |
138 | if(is_qelement(t,NSURI_XRD "\tService") ) { | 138 | if(is_qelement(t,NSURI_XRD "\tService") ) { |
139 | if( !t.auth_info.auth_SEP.xrd_Type.empty() ) { | 139 | if( !t.auth_info.auth_SEP.xrd_Type.empty() ) { |
140 | parser_node_t::attrs_t::const_iterator ip | 140 | parser_node_t::attrs_t::const_iterator ip |
141 | = t.attrs.find("priority"); | 141 | = t.attrs.find("priority"); |
142 | if(ip!=t.attrs.end()) { | 142 | if(ip!=t.attrs.end()) { |
143 | const char *nptr = ip->second.c_str(); | 143 | const char *nptr = ip->second.c_str(); |
144 | char *eptr = 0; | 144 | char *eptr = 0; |
145 | t.auth_info.auth_SEP.priority = strtol(nptr,&eptr,10); | 145 | t.auth_info.auth_SEP.priority = strtol(nptr,&eptr,10); |
146 | if(nptr==eptr) | 146 | if(nptr==eptr) |
147 | t.auth_info.auth_SEP.priority = LONG_MAX; | 147 | t.auth_info.auth_SEP.priority = LONG_MAX; |
148 | } | 148 | } |
149 | if( (t.auth_info.auth_SEP.priority < p.auth_info.auth_SEP.priority) | 149 | if( (t.auth_info.auth_SEP.priority < p.auth_info.auth_SEP.priority) |
150 | || p.auth_info.auth_SEP.xrd_Type.empty() ) | 150 | || p.auth_info.auth_SEP.xrd_Type.empty() ) |
151 | p.auth_info.auth_SEP = t.auth_info.auth_SEP; | 151 | p.auth_info.auth_SEP = t.auth_info.auth_SEP; |
152 | } | 152 | } |
153 | }else if( is_qelement(t,NSURI_XRD "\tCanonicalID") ) | 153 | }else if( is_qelement(t,NSURI_XRD "\tCanonicalID") ) |
154 | copy_trim_whitespace(p.auth_info.canonical_id,t.content); | 154 | copy_trim_whitespace(p.auth_info.canonical_id,t.content); |
155 | } | 155 | } |
156 | } | 156 | } |
157 | 157 | ||
158 | tree.pop(); | 158 | tree.pop(); |
159 | } | 159 | } |
160 | 160 | ||
161 | size_t openid_service_resolver_t::write(void *p,size_t s,size_t nm) { | 161 | size_t openid_service_resolver_t::write(void *p,size_t s,size_t nm) { |
162 | if(state != state_parse) | 162 | if(state != state_parse) |
163 | return 0; | 163 | return 0; |
164 | /* TODO: limit total size */ | 164 | /* TODO: limit total size */ |
165 | size_t bytes = s*nm; | 165 | size_t bytes = s*nm; |
166 | parse((const char *)p,bytes,false); | 166 | parse((const char *)p,bytes,false); |
167 | return bytes; | 167 | return bytes; |
168 | } | 168 | } |
169 | 169 | ||
170 | size_t openid_service_resolver_t::header(void *p,size_t s,size_t nm) { | 170 | size_t openid_service_resolver_t::header(void *p,size_t s,size_t nm) { |
171 | size_t bytes = s*nm; | 171 | size_t bytes = s*nm; |
172 | const char *h = (const char *)p; | 172 | const char *h = (const char *)p; |
173 | const char *colon = (const char*)memchr(p,':',bytes); | 173 | const char *colon = (const char*)memchr(p,':',bytes); |
174 | const char *space = (const char*)memchr(p,' ',bytes); | 174 | const char *space = (const char*)memchr(p,' ',bytes); |
175 | if(space && ( (!colon) || space<colon ) ) { | 175 | if(space && ( (!colon) || space<colon ) ) { |
176 | xrds_location.clear(); http_content_type.clear(); | 176 | xrds_location.clear(); http_content_type.clear(); |
177 | }else if(colon) { | 177 | }else if(colon) { |
178 | const char *hv = ++colon; | 178 | const char *hv = ++colon; |
179 | int hnl = colon-h; | 179 | int hnl = colon-h; |
180 | int rb; | 180 | int rb; |
181 | for(rb = bytes-hnl-1; | 181 | for(rb = bytes-hnl-1; |
182 | rb>0 && isspace(*hv); | 182 | rb>0 && isspace(*hv); |
183 | ++hv,--rb ); | 183 | ++hv,--rb ); |
184 | while(rb>0 && isspace(hv[rb-1])) | 184 | while(rb>0 && isspace(hv[rb-1])) |
185 | --rb; | 185 | --rb; |
186 | if(rb) { | 186 | if(rb) { |
187 | if( (hnl >= sizeof(LOCATION_HEADER)) | 187 | if( (hnl >= sizeof(LOCATION_HEADER)) |
188 | && !strncasecmp(h,LOCATION_HEADER ":", | 188 | && !strncasecmp(h,LOCATION_HEADER ":", |
189 | sizeof(LOCATION_HEADER)) ) { | 189 | sizeof(LOCATION_HEADER)) ) { |
190 | xrds_location.assign(hv,rb); | 190 | xrds_location.assign(hv,rb); |
191 | }else if( (hnl >= sizeof("Content-Type")) | 191 | }else if( (hnl >= sizeof("Content-Type")) |
192 | && !strncasecmp(h,"Content-Type:", | 192 | && !strncasecmp(h,"Content-Type:", |
193 | sizeof("Content-Type")) ) { | 193 | sizeof("Content-Type")) ) { |
194 | const char *sc = (const char*)memchr( | 194 | const char *sc = (const char*)memchr( |
195 | hv,';',rb); | 195 | hv,';',rb); |
196 | http_content_type.assign( | 196 | http_content_type.assign( |
197 | hv,sc?(sc-hv):rb ); | 197 | hv,sc?(sc-hv):rb ); |
198 | } | 198 | } |
199 | } | 199 | } |
200 | } | 200 | } |
201 | return curl_t::header(p,s,nm); | 201 | return curl_t::header(p,s,nm); |
202 | } | 202 | } |
203 | 203 | ||
204 | void openid_service_resolver_t::discover_service(const string& url,bool xri) { | 204 | void openid_service_resolver_t::discover_service(const string& url,bool xri) { |
205 | CURLcode r = easy_setopt(CURLOPT_URL,url.c_str()); | 205 | CURLcode r = easy_setopt(CURLOPT_URL,url.c_str()); |
206 | if(r) | 206 | if(r) |
207 | throw opkele::exception_curl(OPKELE_CP_ "failed to set curly urlie",r); | 207 | throw opkele::exception_curl(OPKELE_CP_ "failed to set curly urlie",r); |
208 | 208 | ||
209 | (*(expat_t*)this) = parser_create_ns(); | 209 | (*(expat_t*)this) = parser_create_ns(); |
210 | set_user_data(); set_element_handler(); | 210 | set_user_data(); set_element_handler(); |
211 | set_character_data_handler(); | 211 | set_character_data_handler(); |
212 | tree.clear(); | 212 | tree.clear(); |
213 | state = state_parse; | 213 | state = state_parse; |
214 | 214 | ||
215 | r = easy_perform(); | 215 | r = easy_perform(); |
216 | if(r && r!=CURLE_WRITE_ERROR) | 216 | if(r && r!=CURLE_WRITE_ERROR) |
217 | throw exception_curl(OPKELE_CP_ "failed to perform curly request",r); | 217 | throw exception_curl(OPKELE_CP_ "failed to perform curly request",r); |
218 | 218 | ||
219 | parse(0,0,true); | 219 | parse(0,0,true); |
220 | while(!tree.empty()) pop_tag(); | 220 | while(!tree.empty()) pop_tag(); |
221 | } | 221 | } |
222 | 222 | ||
223 | const openid_auth_info_t& openid_service_resolver_t::resolve(const string& id) { | 223 | const openid_auth_info_t& openid_service_resolver_t::resolve(const string& id) { |
224 | auth_info = openid_auth_info_t(); | 224 | auth_info = openid_auth_info_t(); |
225 | html_SEP = openid_auth_SEP_t(); | 225 | html_SEP = openid_auth_SEP_t(); |
226 | 226 | ||
227 | string::size_type fns = id.find_first_not_of(whitespace); | 227 | string::size_type fns = id.find_first_not_of(whitespace); |
228 | if(fns==string::npos) | 228 | if(fns==string::npos) |
229 | throw opkele::bad_input(OPKELE_CP_ "whitespace-only identity"); | 229 | throw opkele::bad_input(OPKELE_CP_ "whitespace-only identity"); |
230 | string::size_type lns = id.find_last_not_of(whitespace); | 230 | string::size_type lns = id.find_last_not_of(whitespace); |
231 | assert(lns!=string::npos); | 231 | assert(lns!=string::npos); |
232 | if(!strncasecmp( | 232 | if(!strncasecmp( |
233 | id.c_str()+fns,"xri://", | 233 | id.c_str()+fns,"xri://", |
234 | sizeof("xri://")-1)) | 234 | sizeof("xri://")-1)) |
235 | fns+=sizeof("xri://")-1; | 235 | fns+=sizeof("xri://")-1; |
236 | string nid(id,fns,lns-fns+1); | 236 | string nid(id,fns,lns-fns+1); |
237 | if(nid.empty()) | 237 | if(nid.empty()) |
238 | throw opkele::bad_input(OPKELE_CP_ "nothing significant in identity"); | 238 | throw opkele::bad_input(OPKELE_CP_ "nothing significant in identity"); |
239 | if(strchr("=@+$!(",*nid.c_str())) { | 239 | if(strchr("=@+$!(",*nid.c_str())) { |
240 | discover_service( | 240 | discover_service( |
241 | xri_proxy + util::url_encode(nid) + | 241 | xri_proxy + util::url_encode(nid) + |
242 | "?_xrd_t=" STURI_OPENID10 "&_xrd_r=application/xrd+xml;sep=true", | 242 | "?_xrd_t=" STURI_OPENID10 "&_xrd_r=application/xrd+xml;sep=true", |
243 | true ); | 243 | true ); |
244 | if(auth_info.canonical_id.empty() | 244 | if(auth_info.canonical_id.empty() |
245 | || auth_info.auth_SEP.xrd_Type.empty() ) | 245 | || auth_info.auth_SEP.xrd_Type.empty() ) |
246 | throw opkele::failed_lookup(OPKELE_CP_ "no OpenID service for XRI found"); | 246 | throw opkele::failed_lookup(OPKELE_CP_ "no OpenID service for XRI found"); |
247 | return auth_info; | ||
248 | }else{ | 247 | }else{ |
249 | const char *np = nid.c_str(); | 248 | const char *np = nid.c_str(); |
250 | if( (strncasecmp(np,"http",4) || strncmp( | 249 | if( (strncasecmp(np,"http",4) || strncmp( |
251 | tolower(*(np+4))=='s'? np+5 : np+4, "://", 3)) | 250 | tolower(*(np+4))=='s'? np+5 : np+4, "://", 3)) |
252 | #ifndef NDEBUG | 251 | #ifndef NDEBUG |
253 | && strncasecmp(np,"file:///",sizeof("file:///")-1) | 252 | && strncasecmp(np,"file:///",sizeof("file:///")-1) |
254 | #endif /* XXX: or how do I let tests work? */ | 253 | #endif /* XXX: or how do I let tests work? */ |
255 | ) | 254 | ) |
256 | nid.insert(0,"http://"); | 255 | nid.insert(0,"http://"); |
257 | string::size_type fp = nid.find('#'); | 256 | string::size_type fp = nid.find('#'); |
258 | if(fp!=string::npos) { | 257 | if(fp!=string::npos) { |
259 | string::size_type qp = nid.find('?'); | 258 | string::size_type qp = nid.find('?'); |
260 | if(qp==string::npos || qp<fp) { | 259 | if(qp==string::npos || qp<fp) { |
261 | nid.erase(fp); | 260 | nid.erase(fp); |
262 | }else if(qp>fp) | 261 | }else if(qp>fp) |
263 | nid.erase(fp,qp-fp); | 262 | nid.erase(fp,qp-fp); |
264 | } | 263 | } |
265 | discover_service(nid); | 264 | discover_service(nid); |
266 | const char *eu = 0; | 265 | const char *eu = 0; |
267 | CURLcode r = easy_getinfo(CURLINFO_EFFECTIVE_URL,&eu); | 266 | CURLcode r = easy_getinfo(CURLINFO_EFFECTIVE_URL,&eu); |
268 | if(r) | 267 | if(r) |
269 | throw exception_curl(OPKELE_CP_ "failed to get CURLINFO_EFFECTIVE_URL",r); | 268 | throw exception_curl(OPKELE_CP_ "failed to get CURLINFO_EFFECTIVE_URL",r); |
270 | string canonicalized_id = util::rfc_3986_normalize_uri(eu); | 269 | string canonicalized_id = util::rfc_3986_normalize_uri(eu); |
271 | if(xrds_location.empty()) { | 270 | if(xrds_location.empty()) { |
272 | if(auth_info.auth_SEP.xrd_Type.empty()) { | 271 | if(auth_info.auth_SEP.xrd_Type.empty()) { |
273 | if(html_SEP.xrd_URI.empty()) | 272 | if(html_SEP.xrd_URI.empty()) |
274 | throw opkele::failed_lookup(OPKELE_CP_ "no OpenID service discovered"); | 273 | throw opkele::failed_lookup(OPKELE_CP_ "no OpenID 1.0 service discovered"); |
275 | auth_info.auth_SEP = html_SEP; | 274 | auth_info.auth_SEP = html_SEP; |
276 | auth_info.auth_SEP.xrd_Type.clear(); auth_info.auth_SEP.xrd_Type.insert( STURI_OPENID10 ); | 275 | auth_info.auth_SEP.xrd_Type.clear(); auth_info.auth_SEP.xrd_Type.insert( STURI_OPENID10 ); |
277 | auth_info.canonical_id = canonicalized_id; | 276 | auth_info.canonical_id = canonicalized_id; |
278 | }else{ | 277 | }else{ |
279 | if(auth_info.canonical_id.empty()) | 278 | if(auth_info.canonical_id.empty()) |
280 | auth_info.canonical_id = canonicalized_id; | 279 | auth_info.canonical_id = canonicalized_id; |
281 | } | 280 | } |
282 | return auth_info; | ||
283 | }else{ | 281 | }else{ |
284 | discover_service(xrds_location); | 282 | discover_service(xrds_location); |
285 | if(auth_info.auth_SEP.xrd_Type.empty()) | 283 | if(auth_info.auth_SEP.xrd_Type.empty()) { |
286 | throw opkele::failed_lookup(OPKELE_CP_ "no OpenID service found in Yadis document"); | 284 | if(html_SEP.xrd_URI.empty()) |
287 | if(auth_info.canonical_id.empty()) | 285 | throw opkele::failed_lookup(OPKELE_CP_ "no OpenID 1.0 service discovered"); |
286 | auth_info.auth_SEP = html_SEP; | ||
287 | auth_info.auth_SEP.xrd_Type.clear(); auth_info.auth_SEP.xrd_Type.insert( STURI_OPENID10 ); | ||
288 | auth_info.canonical_id = canonicalized_id; | 288 | auth_info.canonical_id = canonicalized_id; |
289 | return auth_info; | 289 | }else{ |
290 | if(auth_info.canonical_id.empty()) | ||
291 | auth_info.canonical_id = canonicalized_id; | ||
292 | } | ||
290 | } | 293 | } |
291 | } | 294 | } |
295 | return auth_info; | ||
292 | } | 296 | } |
293 | 297 | ||
294 | } | 298 | } |