-rw-r--r-- | configure.ac | 9 | ||||
-rw-r--r-- | include/Makefile.am | 2 | ||||
-rw-r--r-- | include/opkele/discovery.h | 33 | ||||
-rw-r--r-- | include/opkele/exception.h | 19 | ||||
-rw-r--r-- | include/opkele/openid_service_resolver.h | 118 | ||||
-rw-r--r-- | include/opkele/types.h | 63 | ||||
-rw-r--r-- | include/opkele/uris.h | 13 | ||||
-rw-r--r-- | lib/Makefile.am | 2 | ||||
-rw-r--r-- | lib/discovery.cc | 375 | ||||
-rw-r--r-- | lib/openid_service_resolver.cc | 298 | ||||
-rw-r--r-- | lib/util.cc | 22 | ||||
-rw-r--r-- | test/.gitignore | 2 | ||||
-rw-r--r-- | test/Makefile.am | 6 | ||||
-rw-r--r-- | test/idiscover.cc | 49 | ||||
-rw-r--r-- | test/openid_resolve.cc | 36 |
15 files changed, 575 insertions, 472 deletions
diff --git a/configure.ac b/configure.ac index 2b94b41..1bd1873 100644 --- a/configure.ac +++ b/configure.ac @@ -127,12 +127,21 @@ if $nitpick ; then CPPFLAGS="$CPPFLAGS $CPP_NITPICK" CFLAGS="$CFLAGS $C_NITPICK" CXXFLAGS="$CXXFLAGS $CXX_NITPICK" fi +xri_proxy_url="http://beta.xri.net/" +AC_MSG_CHECKING([for XRI resolver proxy]) +AC_ARG_ENABLE([xri-proxy], + AC_HELP_STRING([--with-xri-proxy=url],[set xri proxy for use when resolving xri identities, default is http://xr_proxy_url]), + [ xri_proxy_url="$withval" ] +) +AC_MSG_RESULT([$xri_proxy_url]) +AC_DEFINE_UNQUOTED([XRI_PROXY_URL],["$xri_proxy_url"],[XRI proxy resolver URL]) + AC_CONFIG_FILES([ Makefile libopkele.pc Doxyfile include/Makefile lib/Makefile diff --git a/include/Makefile.am b/include/Makefile.am index 23c7e0d..0c2928d 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -9,11 +9,11 @@ nobase_include_HEADERS = \ opkele/extension.h \ opkele/sreg.h \ opkele/extension_chain.h \ opkele/xconsumer.h \ opkele/xserver.h \ opkele/curl.h opkele/expat.h \ - opkele/openid_service_resolver.h \ + opkele/discovery.h \ opkele/uris.h EXTRA_DIST = \ opkele/data.h \ opkele/util.h diff --git a/include/opkele/discovery.h b/include/opkele/discovery.h new file mode 100644 index 0000000..5d7129b --- a/dev/null +++ b/include/opkele/discovery.h @@ -0,0 +1,33 @@ +#ifndef __OPKELE_DISCOVERY_H +#define __OPKELE_DISCOVERY_H + +#include <string> +#include <opkele/types.h> + +namespace opkele { + using std::string; + + struct idiscovery_t; + + void idiscover(idiscovery_t& result,const string& identity); + + struct idiscovery_t { + string normalized_id; + string canonicalized_id; + xrd::XRD_t xrd; + + idiscovery_t(const string& i) { + idiscover(*this,i); + } + idiscovery_t(const char *i) { + idiscover(*this,i); + } + + void clear() { + normalized_id.clear(); canonicalized_id.clear(); + xrd.clear(); + } + }; +} + +#endif /* __OPKELE_DISCOVERY_H */ diff --git a/include/opkele/exception.h b/include/opkele/exception.h index 753a818..a654d59 100644 --- a/include/opkele/exception.h +++ b/include/opkele/exception.h @@ -200,12 +200,31 @@ namespace opkele { exception_curl(OPKELE_E_PARS); exception_curl(OPKELE_E_PARS,CURLcode e); ~exception_curl() throw() { } }; /** + * exception thrown in case of failed discovery + */ + class failed_discovery : public exception { + public: + failed_discovery(OPKELE_E_PARS) + : exception(OPKELE_E_CONS) { } + }; + + /** + * unsuccessfull xri resolution + */ + class failed_xri_resolution : public failed_discovery { + public: + long _code; + failed_xri_resolution(OPKELE_E_PARS,long _c=-1) + : failed_discovery(OPKELE_E_CONS), _code(_c) { } + }; + + /** * not implemented (think pure virtual) member function executed, signfies * programmer error */ class not_implemented : public exception { public: not_implemented(OPKELE_E_PARS) diff --git a/include/opkele/openid_service_resolver.h b/include/opkele/openid_service_resolver.h deleted file mode 100644 index 64edd28..0000000 --- a/include/opkele/openid_service_resolver.h +++ b/dev/null @@ -1,118 +0,0 @@ -#ifndef __OPKELE_OPENID_SERVICE_RESOLVER_H -#define __OPKELE_OPENID_SERVICE_RESOLVER_H - -#include <climits> -#include <string> -#include <list> -#include <set> -#include <map> -#include <opkele/curl.h> -#include <opkele/expat.h> - -namespace opkele { - using std::list; - using std::string; - using std::set; - using std::map; - - struct openid_auth_SEP_t { - long priority; - set<string> xrd_Type; - string xrd_URI; - string openid_Delegate; - - openid_auth_SEP_t() : priority(LONG_MAX) { } - }; - - struct openid_auth_info_t { - string canonical_id; - openid_auth_SEP_t auth_SEP; - }; - - - class openid_service_resolver_t : public util::curl_t, public util::expat_t { - public: - string xri_proxy; - - openid_service_resolver_t(const string& xp=""); - ~openid_service_resolver_t() throw() { } - - const openid_auth_info_t& resolve(const string& id); - - enum state_t { - state_parse = 0, - state_stopping_head, state_stopping_body, - state_stopping_size - }; - state_t state; - - struct parser_node_t { - string element; - string content; - typedef map<string,string> attrs_t; - attrs_t attrs; - bool skip_text, skip_tags; - openid_auth_info_t auth_info; - - parser_node_t(const XML_Char *n,const XML_Char **a) - : skip_text(true), skip_tags(true) - { - element = n; - for(;*a;a+=2) - attrs[a[0]] = a[1]; - } - - }; - - class parser_tree_t : public list<parser_node_t> { - public: - const_reference top() const { return back(); } - reference top() { return back(); } - - const_reference parent() const { - const_reverse_iterator rv = rbegin(); - return *(++rv); } - reference parent() { - reverse_iterator rv = rbegin(); - return *(++rv); } - - inline void pop() { pop_back(); } - inline void push(const_reference e) { push_back(e); } - - void push(const XML_Char *n,const XML_Char **a) { - parser_node_t nn(n,a); - if(empty()) - nn.skip_text = nn.skip_tags = true; - else{ - const_reference t = top(); - nn.skip_text = t.skip_text; nn.skip_tags = t.skip_tags; - } - push(nn); - } - }; - parser_tree_t tree; - - void start_element(const XML_Char *n,const XML_Char **a); - void end_element(const XML_Char *n); - void character_data(const XML_Char *s,int l); - - string xrds_location; - openid_auth_SEP_t html_SEP; - openid_auth_info_t auth_info; - - void pop_tag(); - - size_t write(void *p,size_t s,size_t nm); - - string http_content_type; - - size_t header(void *p,size_t s,size_t nm); - - bool xri_mode; - - void discover_service(const string& url,bool xri=false); - }; - -} - -#endif /* __OPKELE_OPENID_SERVICE_RESOLVER_H */ diff --git a/include/opkele/types.h b/include/opkele/types.h index f732a1e..520618d 100644 --- a/include/opkele/types.h +++ b/include/opkele/types.h @@ -8,19 +8,22 @@ #include <ostream> #include <vector> #include <string> #include <map> #include <memory> +#include <set> namespace opkele { using std::vector; using std::string; using std::map; using std::ostream; using std::auto_ptr; + using std::multimap; + using std::set; /** * the OpenID operation mode */ typedef enum _mode_t { mode_associate, @@ -164,9 +167,69 @@ namespace opkele { * dump the key/value pairs for the parameters to the stream. * @param o output stream * @param p the parameters */ ostream& operator << (ostream& o,const params_t& p); + namespace xrd { + + struct priority_compare { + inline bool operator()(long a,long b) const { + return (a<0) ? false : (b<0) ? false : (a<b); + } + }; + + template <typename _DT> + class priority_map : public multimap<long,_DT,priority_compare> { + typedef multimap<long,_DT,priority_compare> map_type; + public: + + inline _DT& add(long priority,const _DT& d) { + return insert(typename map_type::value_type(priority,d))->second; + } + }; + + typedef priority_map<string> canonical_ids_t; + typedef priority_map<string> local_ids_t; + typedef set<string> types_t; + typedef priority_map<string> uris_t; + + class service_t { + public: + types_t types; + uris_t uris; + local_ids_t local_ids; + + void clear() { + types.clear(); + uris.clear(); local_ids.clear(); + } + }; + typedef priority_map<service_t> services_t; + + class XRD_t { + public: + time_t expires; + + canonical_ids_t canonical_ids; + local_ids_t local_ids; + services_t services; + + void clear() { + expires = 0; + canonical_ids.clear(); local_ids.clear(); + services.clear(); + } + bool empty() const { + return + canonical_ids.empty() + && local_ids.empty() + && services.empty(); + } + + }; + + } + } #endif /* __OPKELE_TYPES_H */ diff --git a/include/opkele/uris.h b/include/opkele/uris.h index 9a6a3cd..a432b13 100644 --- a/include/opkele/uris.h +++ b/include/opkele/uris.h @@ -1,10 +1,15 @@ #ifndef __OPKELE_URIS_H #define __OPKELE_URIS_H -#define NSURI_XRDS "xri://$xrds" -#define NSURI_XRD "xri://$xrd*($v*2.0)" -#define NSURI_OPENID10 "http://openid.net/xmlns/1.0" +#define NSURI_XRDS "xri://$xrds" +#define NSURI_XRD "xri://$xrd*($v*2.0)" +#define NSURI_OPENID10 "http://openid.net/xmlns/1.0" -#define STURI_OPENID10 "http://openid.net/signon/1.0" +#define STURI_OPENID10 "http://openid.net/signon/1.0" +#define STURI_OPENID11 "http://openid.net/signon/1.1" +#define STURI_OPENID20 "http://specs.openid.net/auth/2.0/signon" +#define STURI_OPENID20_OP "http://specs.openid.net/auth/2.0/server" + +#define IDURI_SELECT20 "http://specs.openid.net/auth/2.0/identifier_select" #endif /* __OPKELE_URIS_H */ diff --git a/lib/Makefile.am b/lib/Makefile.am index b278faf..185411f 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -22,9 +22,9 @@ libopkele_la_SOURCES = \ consumer.cc \ exception.cc \ extension.cc \ sreg.cc \ extension_chain.cc \ curl.cc expat.cc \ - openid_service_resolver.cc + discovery.cc libopkele_la_LDFLAGS = \ -version-info 2:0:0 diff --git a/lib/discovery.cc b/lib/discovery.cc new file mode 100644 index 0000000..a35ce32 --- a/dev/null +++ b/lib/discovery.cc @@ -0,0 +1,375 @@ +#include <iostream> +using namespace std; +#include <list> +#include <opkele/curl.h> +#include <opkele/expat.h> +#include <opkele/uris.h> +#include <opkele/discovery.h> +#include <opkele/exception.h> +#include <opkele/util.h> + +#include "config.h" + +#define XRDS_HEADER "X-XRDS-Location" +#define CT_HEADER "Content-Type" + +namespace opkele { + using std::list; + using xrd::XRD_t; + using xrd::service_t; + + static const char *whitespace = " \t\r\n"; + static const char *i_leaders = "=@+$!("; + + static inline bool is_qelement(const XML_Char *n,const char *qen) { + return !strcasecmp(n,qen); + } + static inline bool is_element(const XML_Char *n,const char *en) { + if(!strcasecmp(n,en)) return true; + int nl = strlen(n), enl = strlen(en); + if( (nl>=(enl+1)) && n[nl-enl-1]=='\t' + && !strcasecmp(&n[nl-enl],en) ) + return true; + return false; + } + + static long element_priority(const XML_Char **a) { + for(;*a;++a) + if(!strcasecmp(*(a++),"priority")) { + long rv; + return (sscanf(*a,"%ld",&rv)==1)?rv:-1; + } + return -1; + } + + class idigger_t : public util::curl_t, public util::expat_t { + public: + string xri_proxy; + + enum { + xmode_html = 1, xmode_xrd = 2 + }; + int xmode; + + string xrds_location; + string http_content_type; + service_t html_openid1; + service_t html_openid2; + string cdata_buf; + long status_code; + string status_string; + + typedef list<string> pt_stack_t; + pt_stack_t pt_stack; + int skipping; + + XRD_t *xrd; + service_t *xrd_service; + string* cdata; + + idigger_t() + : util::curl_t(easy_init()), + util::expat_t(0), + xri_proxy(XRI_PROXY_URL) { + CURLcode r; + (r=misc_sets()) + || (r=set_write()) + || (r=set_header()) + ; + if(r) + throw exception_curl(OPKELE_CP_ "failed to set curly options",r); + } + ~idigger_t() throw() { } + + void discover(idiscovery_t& result,const string& identity) { + result.clear(); + string::size_type fsc = identity.find_first_not_of(whitespace); + if(fsc==string::npos) + throw bad_input(OPKELE_CP_ "whtiespace-only identity"); + string::size_type lsc = identity.find_last_not_of(whitespace); + assert(lsc!=string::npos); + if(!strncasecmp(identity.c_str()+fsc,"xri://",sizeof("xri://")-1)) + fsc += sizeof("xri://")-1; + if((fsc+1)>=lsc) + throw bad_input(OPKELE_CP_ "not a character of importance in identity"); + string id(identity,fsc,lsc-fsc+1); + if(strchr(i_leaders,id[0])) { + result.normalized_id = id; + /* TODO: further canonicalize xri identity? Like folding case or whatever... */ + discover_at( + result, + xri_proxy + util::url_encode(id)+ + "?_xrd_r=application/xrd+xml;sep=false", xmode_xrd); + if(status_code!=100) + throw failed_xri_resolution(OPKELE_CP_ + "XRI resolution failed with '"+status_string+"' message",status_code); + if(result.xrd.canonical_ids.empty()) + throw opkele::failed_discovery(OPKELE_CP_ "No CanonicalID for XRI identity found"); + }else{ + if(id.find("://")==string::npos) + id.insert(0,"http://"); + string::size_type fp = id.find('#'); + if(fp!=string::npos) { + string::size_type qp = id.find('?'); + if(qp==string::npos || qp<fp) + id.erase(fp); + else if(qp>fp) + id.erase(fp,qp-fp); + } + result.normalized_id = util::rfc_3986_normalize_uri(id); + discover_at(result,id,xmode_html|xmode_xrd); + const char * eu = 0; + CURLcode r = easy_getinfo(CURLINFO_EFFECTIVE_URL,&eu); + if(r) + throw exception_curl(OPKELE_CP_ "failed to get CURLINFO_EFFECTIVE_URL",r); + result.canonicalized_id = util::rfc_3986_normalize_uri(eu); /* XXX: strip fragment part? */ + if(xrds_location.empty()) { + html2xrd(result.xrd); + }else{ + discover_at(result,xrds_location,xmode_xrd); + if(result.xrd.empty()) + html2xrd(result.xrd); + } + } + } + + void discover_at(idiscovery_t& result,const string& url,int xm) { + CURLcode r = easy_setopt(CURLOPT_URL,url.c_str()); + if(r) + throw exception_curl(OPKELE_CP_ "failed to set culry urlie",r); + + (*(expat_t*)this) = parser_create_ns(); + set_user_data(); set_element_handler(); + set_character_data_handler(); + + xrds_location.clear(); http_content_type.clear(); + xmode = xm; + if(xmode&xmode_html) { + xrds_location.clear(); + html_openid1.clear(); html_openid2.clear(); + } + xrd = &result.xrd; + cdata = 0; xrd_service = 0; skipping = 0; + status_code = 100; status_string.clear(); + + r = easy_perform(); + if(r && r!=CURLE_WRITE_ERROR) + throw exception_curl(OPKELE_CP_ "failed to perform curly request",r); + + parse(0,0,true); + } + + void html2xrd(XRD_t& x) { + if(!html_openid1.uris.empty()) { + html_openid1.types.insert(STURI_OPENID11); + x.services.add(-1,html_openid1); + } + if(!html_openid2.uris.empty()) { + html_openid2.types.insert(STURI_OPENID20); + x.services.add(-1,html_openid2); + } + } + + size_t write(void *p,size_t s,size_t nm) { + if(skipping<0) return 0; + /* TODO: limit total size */ + size_t bytes = s*nm; + parse((const char *)p,bytes,false); + return bytes; + } + size_t header(void *p,size_t s,size_t nm) { + size_t bytes = s*nm; + const char *h = (const char*)p; + const char *colon = (const char*)memchr(p,':',bytes); + const char *space = (const char*)memchr(p,' ',bytes); + if(space && ( (!colon) || space<colon ) ) { + xrds_location.clear(); http_content_type.clear(); + }else if(colon) { + const char *hv = ++colon; + int hnl = colon-h; + int rb; + for(rb = bytes-hnl-1;rb>0 && isspace(*hv);++hv,--rb); + while(rb>0 && isspace(hv[rb-1])) --rb; + if(rb) { + if( (hnl>=sizeof(XRDS_HEADER)) + && !strncasecmp(h,XRDS_HEADER":", + sizeof(XRDS_HEADER)) ) { + xrds_location.assign(hv,rb); + }else if( (hnl>=sizeof(CT_HEADER)) + && !strncasecmp(h,CT_HEADER":", + sizeof(CT_HEADER)) ) { + const char *sc = (const char*)memchr( + hv,';',rb); + http_content_type.assign(hv,sc?(sc-hv):rb); + } + } + } + return curl_t::header(p,s,nm); + } + + void start_element(const XML_Char *n,const XML_Char **a) { + if(skipping<0) return; + if(skipping) { + if(xmode&xmode_html) + html_start_element(n,a); + ++skipping; return; + } + if(pt_stack.empty()) { + if(is_qelement(n,NSURI_XRDS "\tXRDS")) + return; + if(is_qelement(n,NSURI_XRD "\tXRD")) { + assert(xrd); + xrd->clear(); + pt_stack.push_back(n); + }else if(xmode&xmode_html) { + html_start_element(n,a); + }else{ + skipping = -1; + } + }else{ + int pt_s = pt_stack.size(); + if(pt_s==1) { + /* TODO: xrd:XRD/xrd:Expires */ + if(is_qelement(n,NSURI_XRD "\tCanonicalID")) { + assert(xrd); + cdata = &(xrd->canonical_ids.add(element_priority(a),string())); + }else if(is_qelement(n,NSURI_XRD "\tLocalID")) { + assert(xrd); + cdata = &(xrd->local_ids.add(element_priority(a),string())); + }else if(is_qelement(n,NSURI_XRD "\tService")) { + assert(xrd); + xrd_service = &(xrd->services.add(element_priority(a), + service_t())); + pt_stack.push_back(n); + }else if(is_qelement(n,NSURI_XRD "\tStatus")) { + for(;*a;) { + if(!strcasecmp(*(a++),"code")) { + if(sscanf(*(a++),"%ld",&status_code)==1 && status_code!=100) { + cdata = &status_string; + pt_stack.push_back(n); + break; + } + } + } + }else if(xmode&xmode_html) { + html_start_element(n,a); + }else{ + skipping = 1; + } + }else if(pt_s==2) { + if(is_qelement(pt_stack.back().c_str(), NSURI_XRD "\tService")) { + if(is_qelement(n,NSURI_XRD "\tType")) { + assert(xrd); assert(xrd_service); + cdata_buf.clear(); + cdata = &cdata_buf; + }else if(is_qelement(n,NSURI_XRD "\tURI")) { + assert(xrd); assert(xrd_service); + cdata = &(xrd_service->uris.add(element_priority(a),string())); + }else if(is_qelement(n,NSURI_XRD "\tLocalID") + || is_qelement(n,NSURI_OPENID10 "\tDelegate") ) { + assert(xrd); assert(xrd_service); + cdata = &(xrd_service->uris.add(element_priority(a),string())); + }else{ + skipping = 1; + } + }else + skipping = 1; + }else if(xmode&xmode_html) { + html_start_element(n,a); + }else{ + skipping = 1; + } + } + } + void end_element(const XML_Char *n) { + if(skipping<0) return; + if(skipping) { + --skipping; return; + } + if(is_qelement(n,NSURI_XRD "\tType")) { + assert(xrd); assert(xrd_service); assert(cdata==&cdata_buf); + xrd_service->types.insert(cdata_buf); + }else if(is_qelement(n,NSURI_XRD "\tService")) { + assert(xrd); assert(xrd_service); + assert(!pt_stack.empty()); + assert(pt_stack.back()==(NSURI_XRD "\tService")); + pt_stack.pop_back(); + xrd_service = 0; + }else if(is_qelement(n,NSURI_XRD "\tStatus")) { + assert(xrd); + if(is_qelement(pt_stack.back().c_str(),n)) { + assert(cdata==&status_string); + pt_stack.pop_back(); + if(status_code!=100) + skipping = -1; + } + }else if((xmode&xmode_html) && is_element(n,"head")) { + skipping = -1; + } + cdata = 0; + } + void character_data(const XML_Char *s,int l) { + if(skipping) return; + if(cdata) cdata->append(s,l); + } + + void html_start_element(const XML_Char *n,const XML_Char **a) { + if(is_element(n,"meta")) { + bool heq = false; + string l; + for(;*a;a+=2) { + if(!( strcasecmp(a[0],"http-equiv") + || strcasecmp(a[1],XRDS_HEADER) )) + heq = true; + else if(!strcasecmp(a[0],"content")) + l.assign(a[1]); + } + if(heq) + xrds_location = l; + }else if(is_element(n,"link")) { + string rels; + string href; + for(;*a;a+=2) { + if( !strcasecmp(a[0],"rel") ) { + rels.assign(a[1]); + }else if( !strcasecmp(a[0],"href") ) { + const char *ns = a[1]; + for(;*ns && isspace(*ns);++ns); + href.assign(ns); + string::size_type lns=href.find_last_not_of(whitespace); + href.erase(lns+1); + } + } + for(string::size_type ns=rels.find_first_not_of(whitespace); + ns!=string::npos; ns=rels.find_first_not_of(whitespace,ns)) { + string::size_type s = rels.find_first_of(whitespace,ns); + string rel; + if(s==string::npos) { + rel.assign(rels,ns,string::npos); + ns = string::npos; + }else{ + rel.assign(rels,ns,s-ns); + ns = s; + } + if(rel=="openid.server") + html_openid1.uris.add(-1,href); + else if(rel=="openid.delegate") + html_openid1.local_ids.add(-1,href); + else if(rel=="openid2.provider") + html_openid2.uris.add(-1,href); + else if(rel=="openid2.local_id") + html_openid2.local_ids.add(-1,href); + } + }else if(is_element(n,"body")) { + skipping = -1; + } + } + + }; + + void idiscover(idiscovery_t& result,const string& identity) { + idigger_t idigger; + idigger.discover(result,identity); + } + +} diff --git a/lib/openid_service_resolver.cc b/lib/openid_service_resolver.cc deleted file mode 100644 index 505e5b2..0000000 --- a/lib/openid_service_resolver.cc +++ b/dev/null @@ -1,298 +0,0 @@ -#include <cctype> -#include <opkele/exception.h> -#include <opkele/util.h> -#include <opkele/openid_service_resolver.h> -#include <opkele/uris.h> - -#define LOCATION_HEADER "X-XRDS-Location" - -namespace opkele { - static const char *whitespace = " \t\r\n"; - - openid_service_resolver_t::openid_service_resolver_t(const string& xp) - : util::curl_t(easy_init()), - util::expat_t(0), - xri_proxy(xp.empty()?"http://beta.xri.net/":xp) - { - CURLcode r; - (r=misc_sets()) - || (r=set_write()) - || (r==set_header()) - ; - if(r) - throw opkele::exception_curl(OPKELE_CP_ "failed to set curly options",r); - } - - static bool is_element(const XML_Char *n,const char *en) { - if(!strcasecmp(n,en)) return true; - int nl = strlen(n), enl = strlen(en); - if( (nl>=(enl+1)) && n[nl-enl-1]=='\t' - && !strcasecmp(&n[nl-enl],en) ) - return true; - return false; - } - static inline bool is_qelement(const XML_Char *n,const char *qen) { - return !strcasecmp(n,qen); - } - static inline bool is_element( - const openid_service_resolver_t::parser_node_t& n, - const char *en) { - return is_element(n.element.c_str(),en); - } - static inline bool is_qelement( - const openid_service_resolver_t::parser_node_t& n, - const char *qen) { - return is_qelement(n.element.c_str(),qen); - } - - void openid_service_resolver_t::start_element(const XML_Char *n,const XML_Char **a) { - if(state!=state_parse) return; - tree.push(n,a); - parser_node_t& t = tree.top(); - if(is_element(n,"html") || is_element(n,"head") - || is_qelement(n,NSURI_XRDS "\tXRDS") - || is_qelement(n,NSURI_XRD "\tXRD") ) - t.skip_tags = false; - else if(is_qelement(n,NSURI_XRD "\tService") - || is_qelement(n,NSURI_XRD "\tType") - || is_qelement(n,NSURI_XRD "\tURI") - || is_qelement(n,NSURI_OPENID10 "\tDelegate") - || is_qelement(n,NSURI_XRD "\tCanonicalID") ) - t.skip_tags = t.skip_text = false; - else if(is_element(n,"body")) - state = state_stopping_body; - } - void openid_service_resolver_t::end_element(const XML_Char *n) { - if(state!=state_parse) return; - assert(tree.top().element == n); - pop_tag(); - } - void openid_service_resolver_t::character_data(const XML_Char *s,int l) { - if(state!=state_parse) return; - if( !( tree.empty() || tree.top().skip_text ) ) - tree.top().content.append(s,l); - } - - static void copy_trim_whitespace(string& to,const string& from) { - string::size_type ns0 = from.find_first_not_of(whitespace); - if(ns0==string::npos) { - to.clear(); return; - } - string::size_type ns1 = from.find_last_not_of(whitespace); - assert(ns1!=string::npos); - to.assign(from,ns0,ns1-ns0+1); - } - - void openid_service_resolver_t::pop_tag() { - assert(!tree.empty()); - parser_node_t& t = tree.top(); - if( is_element(t,"meta") - && !strcasecmp(t.attrs["http-equiv"].c_str(),LOCATION_HEADER) ) { - xrds_location = t.attrs["content"]; - }else if( is_element(t,"link") ) { - parser_node_t::attrs_t::const_iterator ir = t.attrs.find("rel"); - if(ir!=t.attrs.end()) { - const string& rels = ir->second; - for(string::size_type ns = rels.find_first_not_of(whitespace); - ns!=string::npos; - ns=rels.find_first_not_of(whitespace,ns)) { - string::size_type s = rels.find_first_of(whitespace,ns); - string rel; - if(s==string::npos) { - rel.assign(rels,ns,string::npos); - ns = string::npos; - }else{ - rel.assign(rels,ns,s-ns); - ns = s; - } - if(rel=="openid.server") - copy_trim_whitespace(html_SEP.xrd_URI,t.attrs["href"]); - else if(rel=="openid.delegate") - copy_trim_whitespace(html_SEP.openid_Delegate,t.attrs["href"]); - } - } - }else if( is_element(t,"head") ) - state = state_stopping_head; - else if( is_qelement(t,NSURI_XRD "\tXRD")) { - if( !( - ( - xri_mode - && t.auth_info.canonical_id.empty() - ) || - t.auth_info.auth_SEP.xrd_Type.empty() - ) ) - auth_info = t.auth_info; - }else if( tree.size()>1 ) { - parser_node_t& p = tree.parent(); - if( is_qelement(p,NSURI_XRD "\tService") ) { - if( is_qelement(t,NSURI_XRD "\tType") ) { - if(t.content==STURI_OPENID10) { - string tmp; copy_trim_whitespace(tmp,t.content); - p.auth_info.auth_SEP.xrd_Type.insert(tmp); - } - }else if( is_qelement(t,NSURI_XRD "\tURI") ) - copy_trim_whitespace(p.auth_info.auth_SEP.xrd_URI,t.content); - else if( is_qelement(t,NSURI_OPENID10 "\tDelegate") ) - copy_trim_whitespace(p.auth_info.auth_SEP.openid_Delegate,t.content); - }else if( is_qelement(p,NSURI_XRD "\tXRD") ) { - if(is_qelement(t,NSURI_XRD "\tService") ) { - if( !t.auth_info.auth_SEP.xrd_Type.empty() ) { - parser_node_t::attrs_t::const_iterator ip - = t.attrs.find("priority"); - if(ip!=t.attrs.end()) { - const char *nptr = ip->second.c_str(); - char *eptr = 0; - t.auth_info.auth_SEP.priority = strtol(nptr,&eptr,10); - if(nptr==eptr) - t.auth_info.auth_SEP.priority = LONG_MAX; - } - if( (t.auth_info.auth_SEP.priority < p.auth_info.auth_SEP.priority) - || p.auth_info.auth_SEP.xrd_Type.empty() ) - p.auth_info.auth_SEP = t.auth_info.auth_SEP; - } - }else if( is_qelement(t,NSURI_XRD "\tCanonicalID") ) - copy_trim_whitespace(p.auth_info.canonical_id,t.content); - } - } - - tree.pop(); - } - - size_t openid_service_resolver_t::write(void *p,size_t s,size_t nm) { - if(state != state_parse) - return 0; - /* TODO: limit total size */ - size_t bytes = s*nm; - parse((const char *)p,bytes,false); - return bytes; - } - - size_t openid_service_resolver_t::header(void *p,size_t s,size_t nm) { - size_t bytes = s*nm; - const char *h = (const char *)p; - const char *colon = (const char*)memchr(p,':',bytes); - const char *space = (const char*)memchr(p,' ',bytes); - if(space && ( (!colon) || space<colon ) ) { - xrds_location.clear(); http_content_type.clear(); - }else if(colon) { - const char *hv = ++colon; - int hnl = colon-h; - int rb; - for(rb = bytes-hnl-1; - rb>0 && isspace(*hv); - ++hv,--rb ); - while(rb>0 && isspace(hv[rb-1])) - --rb; - if(rb) { - if( (hnl >= sizeof(LOCATION_HEADER)) - && !strncasecmp(h,LOCATION_HEADER ":", - sizeof(LOCATION_HEADER)) ) { - xrds_location.assign(hv,rb); - }else if( (hnl >= sizeof("Content-Type")) - && !strncasecmp(h,"Content-Type:", - sizeof("Content-Type")) ) { - const char *sc = (const char*)memchr( - hv,';',rb); - http_content_type.assign( - hv,sc?(sc-hv):rb ); - } - } - } - return curl_t::header(p,s,nm); - } - - void openid_service_resolver_t::discover_service(const string& url,bool xri) { - CURLcode r = easy_setopt(CURLOPT_URL,url.c_str()); - if(r) - throw opkele::exception_curl(OPKELE_CP_ "failed to set curly urlie",r); - - (*(expat_t*)this) = parser_create_ns(); - set_user_data(); set_element_handler(); - set_character_data_handler(); - tree.clear(); - state = state_parse; - - r = easy_perform(); - if(r && r!=CURLE_WRITE_ERROR) - throw exception_curl(OPKELE_CP_ "failed to perform curly request",r); - - parse(0,0,true); - while(!tree.empty()) pop_tag(); - } - - const openid_auth_info_t& openid_service_resolver_t::resolve(const string& id) { - auth_info = openid_auth_info_t(); - html_SEP = openid_auth_SEP_t(); - - string::size_type fns = id.find_first_not_of(whitespace); - if(fns==string::npos) - throw opkele::bad_input(OPKELE_CP_ "whitespace-only identity"); - string::size_type lns = id.find_last_not_of(whitespace); - assert(lns!=string::npos); - if(!strncasecmp( - id.c_str()+fns,"xri://", - sizeof("xri://")-1)) - fns+=sizeof("xri://")-1; - string nid(id,fns,lns-fns+1); - if(nid.empty()) - throw opkele::bad_input(OPKELE_CP_ "nothing significant in identity"); - if(strchr("=@+$!(",*nid.c_str())) { - discover_service( - xri_proxy + util::url_encode(nid) + - "?_xrd_t=" STURI_OPENID10 "&_xrd_r=application/xrd+xml;sep=true", - true ); - if(auth_info.canonical_id.empty() - || auth_info.auth_SEP.xrd_Type.empty() ) - throw opkele::failed_lookup(OPKELE_CP_ "no OpenID service for XRI found"); - }else{ - const char *np = nid.c_str(); - if( (strncasecmp(np,"http",4) || strncmp( - tolower(*(np+4))=='s'? np+5 : np+4, "://", 3)) -#ifndef NDEBUG - && strncasecmp(np,"file:///",sizeof("file:///")-1) -#endif /* XXX: or how do I let tests work? */ - ) - nid.insert(0,"http://"); - string::size_type fp = nid.find('#'); - if(fp!=string::npos) { - string::size_type qp = nid.find('?'); - if(qp==string::npos || qp<fp) { - nid.erase(fp); - }else if(qp>fp) - nid.erase(fp,qp-fp); - } - discover_service(nid); - const char *eu = 0; - CURLcode r = easy_getinfo(CURLINFO_EFFECTIVE_URL,&eu); - if(r) - throw exception_curl(OPKELE_CP_ "failed to get CURLINFO_EFFECTIVE_URL",r); - string canonicalized_id = util::rfc_3986_normalize_uri(eu); - if(xrds_location.empty()) { - if(auth_info.auth_SEP.xrd_Type.empty()) { - if(html_SEP.xrd_URI.empty()) - throw opkele::failed_lookup(OPKELE_CP_ "no OpenID 1.0 service discovered"); - auth_info.auth_SEP = html_SEP; - auth_info.auth_SEP.xrd_Type.clear(); auth_info.auth_SEP.xrd_Type.insert( STURI_OPENID10 ); - auth_info.canonical_id = canonicalized_id; - }else{ - if(auth_info.canonical_id.empty()) - auth_info.canonical_id = canonicalized_id; - } - }else{ - discover_service(xrds_location); - if(auth_info.auth_SEP.xrd_Type.empty()) { - if(html_SEP.xrd_URI.empty()) - throw opkele::failed_lookup(OPKELE_CP_ "no OpenID 1.0 service discovered"); - auth_info.auth_SEP = html_SEP; - auth_info.auth_SEP.xrd_Type.clear(); auth_info.auth_SEP.xrd_Type.insert( STURI_OPENID10 ); - auth_info.canonical_id = canonicalized_id; - }else{ - if(auth_info.canonical_id.empty()) - auth_info.canonical_id = canonicalized_id; - } - } - } - return auth_info; - } - -} diff --git a/lib/util.cc b/lib/util.cc index 69d37b5..83f0eef 100644 --- a/lib/util.cc +++ b/lib/util.cc @@ -182,27 +182,29 @@ namespace opkele { if(colon==string::npos) throw bad_input(OPKELE_CP_ "No scheme specified in URI"); transform( uri.begin()+ns, uri.begin()+colon+1, back_inserter(rv), ::tolower ); bool s; - if(rv=="http:") - s = false; - else if(rv=="https:") - s = true; -#ifndef NDEBUG - else if(rv=="file:") - s = false; -#endif /* XXX: or try to make tests work some other way */ - else - throw not_implemented(OPKELE_CP_ "Only http(s) URIs can be normalized here"); string::size_type ul = uri.find_last_not_of(whitespace)+1; if(ul <= (colon+3)) throw bad_input(OPKELE_CP_ "Unexpected end of URI being normalized encountered"); if(uri[colon+1]!='/' || uri[colon+2]!='/') throw bad_input(OPKELE_CP_ "Unexpected input in URI being normalized after scheme component"); + if(rv=="http:") + s = false; + else if(rv=="https:") + s = true; + else{ + /* TODO: support more schemes. + * e.g. xri. How do we normalize + * xri? + */ + rv.append(uri,colon+1,ul-colon-1); + return rv; + } rv += "//"; string::size_type interesting = uri.find_first_of(":/#?",colon+3); if(interesting==string::npos) { transform( uri.begin()+colon+3,uri.begin()+ul, back_inserter(rv), ::tolower ); diff --git a/test/.gitignore b/test/.gitignore index 5ce4dc9..31ae686 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -1,5 +1,5 @@ /.deps /.libs /test *.o -/openid_resolve +/idiscover diff --git a/test/Makefile.am b/test/Makefile.am index 5aa87b3..13c4cd2 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -1,15 +1,15 @@ -noinst_PROGRAMS = test openid_resolve +noinst_PROGRAMS = test idiscover DEFAULT_INCLUDES = -I${top_builddir} INCLUDES = -I${top_srcdir}/include/ ${KONFORKA_CFLAGS} test_SOURCES = test.cc test_LDADD = ${top_builddir}/lib/libopkele.la EXTRA_DIST=$(addsuffix .html,$(addprefix html/, \ empty head-in-body hkn-delegate hkn-server hkn in-body \ unclosed-head spaced-links spaced-link-attrs 2rels \ )) -openid_resolve_SOURCES = openid_resolve.cc -openid_resolve_LDADD = ${top_builddir}/lib/libopkele.la +idiscover_SOURCES = idiscover.cc +idiscover_LDADD = ${top_builddir}/lib/libopkele.la diff --git a/test/idiscover.cc b/test/idiscover.cc new file mode 100644 index 0000000..2abedc9 --- a/dev/null +++ b/test/idiscover.cc @@ -0,0 +1,49 @@ +#include <iostream> +#include <stdexcept> +#include <iterator> +#include <algorithm> +using namespace std; +#include <opkele/exception.h> +#include <opkele/discovery.h> + +template<typename _PDT> + ostream& operator<<(ostream& o,const opkele::xrd::priority_map<_PDT>& pm) { + for(typename opkele::xrd::priority_map<_PDT>::const_iterator i=pm.begin(); + i!=pm.end();++i) + o << ' ' << i->second << '[' << i->first << ']'; + return o; + } + +ostream& operator<<(ostream& o,const opkele::xrd::service_t s) { + o << "{" << endl + << " Type: "; + copy(s.types.begin(),s.types.end(), + ostream_iterator<string>(o," ")); + o << endl + << " URI: " << s.uris << endl + << " LocalID: " << s.local_ids << endl; + o << "}"; +} + +int main(int argc,char **argv) { + try { + if(argc<2) + throw opkele::exception(OPKELE_CP_ "Please, give me something to resolve"); + for(int a=1;a<argc;++a) { + opkele::idiscovery_t discovery(argv[a]); + clog + << "===============================================================" << endl + << "User-supplied ID: " << argv[a] << endl + << "Normalized ID: " << discovery.normalized_id << endl + << "Canonicalized ID: " << discovery.canonicalized_id << endl + << endl + << "CanonicalID: " << discovery.xrd.canonical_ids << endl + << "LocalID: " << discovery.xrd.local_ids << endl + << "Services: " << discovery.xrd.services << endl; + } + }catch(exception& e) { + cerr << "oops: " << e.what() << endl; + _exit(1); + } + _exit(0); +} diff --git a/test/openid_resolve.cc b/test/openid_resolve.cc deleted file mode 100644 index 31c7a6a..0000000 --- a/test/openid_resolve.cc +++ b/dev/null @@ -1,36 +0,0 @@ -#include <iostream> -#include <stdexcept> -#include <iterator> -#include <algorithm> -using namespace std; -#include <opkele/exception.h> -#include <opkele/openid_service_resolver.h> - -int main(int argc,char **argv) { - try { - if(argc<2) - throw opkele::exception(OPKELE_CP_ "Please, give me something to resolve"); - opkele::openid_service_resolver_t resolver; - for(int a=1;a<argc;++a) { - const opkele::openid_auth_info_t& iai = resolver.resolve(argv[a]); - clog - << "====================" << endl - << "canonical id is " << iai.canonical_id << endl - << endl - << "service priority is " << iai.auth_SEP.priority << endl - << "service types are " ; - copy( - iai.auth_SEP.xrd_Type.begin(), iai.auth_SEP.xrd_Type.end(), - ostream_iterator<string>(clog," ") ); - clog << endl - << "service URI is " << iai.auth_SEP.xrd_URI << endl; - if(!iai.auth_SEP.openid_Delegate.empty()) - clog << "openid:Delegate is " << iai.auth_SEP.openid_Delegate << endl; - clog << endl; - } - }catch(exception& e) { - cerr << "oops: " << e.what() << endl; - _exit(1); - } - _exit(0); -} |