libzypp 9.41.1
|
00001 /*---------------------------------------------------------------------\ 00002 | ____ _ __ __ ___ | 00003 | |__ / \ / / . \ . \ | 00004 | / / \ V /| _/ _/ | 00005 | / /__ | | | | | | | 00006 | /_____||_| |_| |_| | 00007 | | 00008 \---------------------------------------------------------------------*/ 00013 #include <zypp/Url.h> 00014 #include <zypp/Pathname.h> 00015 #include <zypp/base/Gettext.h> 00016 #include <zypp/base/String.h> 00017 #include <zypp/base/Regex.h> 00018 #include <stdexcept> 00019 #include <iostream> 00020 00021 00023 namespace zypp 00024 { 00025 00026 00027 using namespace zypp::url; 00028 00029 00030 // ----------------------------------------------------------------- 00031 /* 00032 * url = [scheme:] [//authority] /path [?query] [#fragment] 00033 */ 00034 #define RX_SPLIT_URL "^([^:/?#]+:|)" \ 00035 "(//[^/?#]*|)" \ 00036 "([^?#]*)" \ 00037 "([?][^#]*|)" \ 00038 "(#.*|)" 00039 00040 00042 namespace 00043 { 00044 00045 00046 // --------------------------------------------------------------- 00047 class LDAPUrl: public UrlBase 00048 { 00049 public: 00050 LDAPUrl(): UrlBase() 00051 { 00052 configure(); 00053 } 00054 00055 LDAPUrl(const LDAPUrl &url): UrlBase(url) 00056 {} 00057 00058 virtual UrlBase * 00059 clone() const 00060 { 00061 return new LDAPUrl(*this); 00062 } 00063 00064 virtual UrlSchemes 00065 getKnownSchemes() const 00066 { 00067 UrlSchemes schemes(2); 00068 schemes[0] = "ldap"; 00069 schemes[1] = "ldaps"; 00070 return schemes; 00071 } 00072 00073 virtual void 00074 configure() 00075 { 00076 config("sep_pathparams", ""); 00077 00078 config("psep_querystr", "?"); 00079 config("vsep_querystr", ""); 00080 00081 // host is required (isValid=>false) 00082 // but not mandatory (see RFC 2255), 00083 // that is, accept empty host. 00084 config("require_host", "y"); 00085 00086 // not allowed here 00087 config("rx_username", ""); 00088 config("rx_password", ""); 00089 config("rx_fragment", ""); 00090 config("rx_pathparams", ""); 00091 } 00092 00093 virtual zypp::url::ParamMap 00094 getQueryStringMap(zypp::url::EEncoding eflag) const 00095 { 00096 static const char * const keys[] = { 00097 "attrs", "scope", "filter", "exts", NULL 00098 }; 00099 zypp::url::ParamMap pmap; 00100 zypp::url::ParamVec pvec( getQueryStringVec()); 00101 if( pvec.size() <= 4) 00102 { 00103 for(size_t i=0; i<pvec.size(); i++) 00104 { 00105 if(eflag == zypp::url::E_ENCODED) 00106 pmap[keys[i]] = pvec[i]; 00107 else 00108 pmap[keys[i]] = zypp::url::decode( pvec[i]); 00109 } 00110 } 00111 else 00112 { 00113 ZYPP_THROW(url::UrlNotSupportedException( 00114 _("Invalid LDAP URL query string") 00115 )); 00116 } 00117 return pmap; 00118 } 00119 00120 virtual void 00121 setQueryStringMap(const zypp::url::ParamMap &pmap) 00122 { 00123 static const char * const keys[] = { 00124 "attrs", "scope", "filter", "exts", NULL 00125 }; 00126 00127 // remove psep ("?") from safe chars 00128 std::string join_safe; 00129 std::string safe(config("safe_querystr")); 00130 std::string psep(config("psep_querystr")); 00131 for(std::string::size_type i=0; i<safe.size(); i++) 00132 { 00133 if( psep.find(safe[i]) == std::string::npos) 00134 join_safe.append(1, safe[i]); 00135 } 00136 00137 zypp::url::ParamVec pvec(4); 00138 zypp::url::ParamMap::const_iterator p; 00139 for(p=pmap.begin(); p!=pmap.end(); ++p) 00140 { 00141 bool found=false; 00142 for(size_t i=0; i<4; i++) 00143 { 00144 if(p->first == keys[i]) 00145 { 00146 found=true; 00147 pvec[i] = zypp::url::encode(p->second, join_safe); 00148 } 00149 } 00150 if( !found) 00151 { 00152 ZYPP_THROW(url::UrlNotSupportedException( 00153 str::form(_("Invalid LDAP URL query parameter '%s'"), 00154 p->first.c_str()) 00155 )); 00156 } 00157 } 00158 setQueryStringVec(pvec); 00159 } 00160 }; 00161 00162 00163 // --------------------------------------------------------------- 00164 // FIXME: hmm.. 00165 class UrlByScheme 00166 { 00167 private: 00168 typedef std::map<std::string,UrlRef> UrlBySchemeMap; 00169 UrlBySchemeMap urlByScheme; 00170 00171 public: 00172 UrlByScheme() 00173 { 00174 UrlRef ref; 00175 00176 // ===================================== 00177 ref.reset( new LDAPUrl()); 00178 addUrlByScheme("ldap", ref); 00179 addUrlByScheme("ldaps", ref); 00180 00181 00182 // ===================================== 00183 ref.reset( new UrlBase()); 00184 ref->config("with_authority", "n"); // disallow host,... 00185 ref->config("require_pathname", "m"); // path is mandatory 00186 addUrlByScheme("hd", ref); 00187 addUrlByScheme("cd", ref); 00188 addUrlByScheme("dvd", ref); 00189 addUrlByScheme("dir", ref); 00190 addUrlByScheme("iso", ref); 00191 00192 // don't show empty authority 00193 ref->setViewOptions( zypp::url::ViewOption::DEFAULTS - 00194 zypp::url::ViewOption::EMPTY_AUTHORITY); 00195 addUrlByScheme("mailto", ref); 00196 addUrlByScheme("urn", ref); 00197 addUrlByScheme("plugin", ref); // zypp plugable media handler: 00198 00199 // RFC1738, 3.10: may contain a host 00200 ref->config("with_authority", "y"); // allow host, 00201 ref->config("with_port", "n"); // but no port, 00202 ref->config("rx_username", ""); // username or 00203 ref->config("rx_password", ""); // password ... 00204 addUrlByScheme("file", ref); 00205 00206 // ===================================== 00207 ref.reset( new UrlBase()); 00208 ref->config("require_host", "m"); // host is mandatory 00209 addUrlByScheme("nfs", ref); 00210 addUrlByScheme("nfs4", ref); 00211 addUrlByScheme("smb", ref); 00212 addUrlByScheme("cifs", ref); 00213 addUrlByScheme("http", ref); 00214 addUrlByScheme("https", ref); 00215 ref->config("path_encode_slash2", "y"); // always encode 2. slash 00216 addUrlByScheme("ftp", ref); 00217 addUrlByScheme("sftp", ref); 00218 addUrlByScheme("tftp", ref); 00219 } 00220 00221 bool 00222 addUrlByScheme(const std::string &scheme, 00223 UrlRef urlImpl) 00224 { 00225 if( urlImpl && urlImpl->isValidScheme(scheme)) 00226 { 00227 UrlRef ref(urlImpl); 00228 ref->clear(); 00229 urlByScheme[str::toLower(scheme)] = ref; 00230 return true; 00231 } 00232 return false; 00233 } 00234 00235 UrlRef 00236 getUrlByScheme(const std::string &scheme) const 00237 { 00238 UrlBySchemeMap::const_iterator i(urlByScheme.find(str::toLower(scheme))); 00239 if( i != urlByScheme.end()) 00240 { 00241 return i->second; 00242 } 00243 return UrlRef(); 00244 } 00245 00246 bool 00247 isRegisteredScheme(const std::string &scheme) const 00248 { 00249 return urlByScheme.find(str::toLower(scheme)) != urlByScheme.end(); 00250 } 00251 00252 UrlSchemes 00253 getRegisteredSchemes() const 00254 { 00255 UrlBySchemeMap::const_iterator i(urlByScheme.begin()); 00256 UrlSchemes schemes; 00257 00258 schemes.reserve(urlByScheme.size()); 00259 for( ; i != urlByScheme.end(); ++i) 00260 { 00261 schemes.push_back(i->first); 00262 } 00263 return schemes; 00264 } 00265 }; 00266 00267 00268 // --------------------------------------------------------------- 00269 UrlByScheme & g_urlSchemeRepository() 00270 { 00271 static UrlByScheme _v; 00272 return _v; 00273 } 00274 00276 } // anonymous namespace 00278 00279 00280 // ----------------------------------------------------------------- 00281 Url::~Url() 00282 { 00283 } 00284 00285 00286 // ----------------------------------------------------------------- 00287 Url::Url() 00288 : m_impl( new UrlBase()) 00289 { 00290 } 00291 00292 00293 // ----------------------------------------------------------------- 00294 Url::Url(const Url &url) 00295 : m_impl( url.m_impl) 00296 { 00297 if( !m_impl) 00298 { 00299 ZYPP_THROW(url::UrlException( 00300 _("Unable to clone Url object") 00301 )); 00302 } 00303 } 00304 00305 00306 // ----------------------------------------------------------------- 00307 Url::Url(const zypp::url::UrlRef &url) 00308 : m_impl( url) 00309 { 00310 if( !m_impl) 00311 { 00312 ZYPP_THROW(url::UrlException( 00313 _("Invalid empty Url object reference") 00314 )); 00315 } 00316 } 00317 00318 00319 // ----------------------------------------------------------------- 00320 Url::Url(const std::string &encodedUrl) 00321 : m_impl( parseUrl(encodedUrl)) 00322 { 00323 if( !m_impl) 00324 { 00325 ZYPP_THROW(url::UrlParsingException( 00326 _("Unable to parse Url components") 00327 )); 00328 } 00329 } 00330 00331 00332 // ----------------------------------------------------------------- 00333 Url& 00334 Url::operator = (const std::string &encodedUrl) 00335 { 00336 UrlRef url( parseUrl(encodedUrl)); 00337 if( !url) 00338 { 00339 ZYPP_THROW(url::UrlParsingException( 00340 _("Unable to parse Url components") 00341 )); 00342 } 00343 m_impl = url; 00344 return *this; 00345 } 00346 00347 00348 // ----------------------------------------------------------------- 00349 Url& 00350 Url::operator = (const Url &url) 00351 { 00352 m_impl = url.m_impl; 00353 return *this; 00354 } 00355 00356 00357 // ----------------------------------------------------------------- 00358 // static 00359 bool 00360 Url::registerScheme(const std::string &scheme, 00361 UrlRef urlImpl) 00362 { 00363 return g_urlSchemeRepository().addUrlByScheme(scheme, urlImpl); 00364 } 00365 00366 00367 // ----------------------------------------------------------------- 00368 // static 00369 UrlRef 00370 Url::parseUrl(const std::string &encodedUrl) 00371 { 00372 UrlRef url; 00373 str::smatch out; 00374 bool ret = false; 00375 00376 try 00377 { 00378 str::regex rex(RX_SPLIT_URL); 00379 ret = str::regex_match(encodedUrl, out, rex); 00380 } 00381 catch( ... ) 00382 {} 00383 00384 if(ret && out.size() == 5) 00385 { 00386 std::string scheme = out[1]; 00387 if (scheme.size() > 1) 00388 scheme = scheme.substr(0, scheme.size()-1); 00389 std::string authority = out[2]; 00390 if (authority.size() >= 2) 00391 authority = authority.substr(2); 00392 std::string query = out[4]; 00393 if (query.size() > 1) 00394 query = query.substr(1); 00395 std::string fragment = out[5]; 00396 if (fragment.size() > 1) 00397 fragment = fragment.substr(1); 00398 00399 url = g_urlSchemeRepository().getUrlByScheme(scheme); 00400 if( !url) 00401 { 00402 url.reset( new UrlBase()); 00403 } 00404 url->init(scheme, authority, out[3], 00405 query, fragment); 00406 } 00407 return url; 00408 } 00409 00410 00411 // ----------------------------------------------------------------- 00412 // static 00413 zypp::url::UrlSchemes 00414 Url::getRegisteredSchemes() 00415 { 00416 return g_urlSchemeRepository().getRegisteredSchemes(); 00417 } 00418 00419 00420 // ----------------------------------------------------------------- 00421 // static 00422 bool 00423 Url::isRegisteredScheme(const std::string &scheme) 00424 { 00425 return g_urlSchemeRepository().isRegisteredScheme(scheme); 00426 } 00427 00428 00429 // ----------------------------------------------------------------- 00430 zypp::url::UrlSchemes 00431 Url::getKnownSchemes() const 00432 { 00433 return m_impl->getKnownSchemes(); 00434 } 00435 00436 00437 // ----------------------------------------------------------------- 00438 bool 00439 Url::isValidScheme(const std::string &scheme) const 00440 { 00441 return m_impl->isValidScheme(scheme); 00442 } 00443 00444 00446 namespace 00447 { 00448 inline bool isInList( const char ** begin_r, const char ** end_r, const std::string & scheme_r ) 00449 { 00450 for ( ; begin_r != end_r; ++begin_r ) 00451 if ( scheme_r == *begin_r ) 00452 return true; 00453 return false; 00454 } 00455 } 00456 bool Url::schemeIsLocal( const std::string & scheme_r ) 00457 { 00458 static const char * val[] = { "cd", "dvd", "dir", "hd", "iso", "file" }; 00459 return isInList( arrayBegin(val), arrayEnd(val), scheme_r ); 00460 } 00461 00462 bool Url::schemeIsRemote( const std::string & scheme_r ) 00463 { 00464 static const char * val[] = { "http", "https", "nfs", "nfs4", "smb", "cifs", "ftp", "sftp", "tftp" }; 00465 return isInList( arrayBegin(val), arrayEnd(val), scheme_r ); 00466 } 00467 00468 bool Url::schemeIsVolatile( const std::string & scheme_r ) 00469 { 00470 static const char * val[] = { "cd", "dvd" }; 00471 return isInList( arrayBegin(val), arrayEnd(val), scheme_r ); 00472 } 00473 00474 bool Url::schemeIsDownloading( const std::string & scheme_r ) 00475 { 00476 static const char * val[] = { "http", "https", "ftp", "sftp", "tftp" }; 00477 return isInList( arrayBegin(val), arrayEnd(val), scheme_r ); 00478 } 00480 00481 // ----------------------------------------------------------------- 00482 bool 00483 Url::isValid() const 00484 { 00485 return m_impl->isValid(); 00486 } 00487 00488 00489 // ----------------------------------------------------------------- 00490 std::string 00491 Url::asString() const 00492 { 00493 return m_impl->asString(); 00494 } 00495 00496 00497 // ----------------------------------------------------------------- 00498 std::string 00499 Url::asCompleteString() const 00500 { 00501 // make sure, all url components are included; 00502 // regardless of the current configuration... 00503 ViewOptions opts(getViewOptions() + 00504 ViewOption::WITH_SCHEME + 00505 ViewOption::WITH_USERNAME + 00506 ViewOption::WITH_PASSWORD + 00507 ViewOption::WITH_HOST + 00508 ViewOption::WITH_PORT + 00509 ViewOption::WITH_PATH_NAME + 00510 ViewOption::WITH_PATH_PARAMS + 00511 ViewOption::WITH_QUERY_STR + 00512 ViewOption::WITH_FRAGMENT); 00513 return m_impl->asString(opts); 00514 } 00515 00516 00517 // ----------------------------------------------------------------- 00518 std::string 00519 Url::asString(const ViewOptions &opts) const 00520 { 00521 return m_impl->asString(opts); 00522 } 00523 00524 00525 // ----------------------------------------------------------------- 00526 std::string 00527 Url::getScheme() const 00528 { 00529 return m_impl->getScheme(); 00530 } 00531 00532 00533 // ----------------------------------------------------------------- 00534 std::string 00535 Url::getAuthority() const 00536 { 00537 return m_impl->getAuthority(); 00538 } 00539 00540 // ----------------------------------------------------------------- 00541 std::string 00542 Url::getPathData() const 00543 { 00544 return m_impl->getPathData(); 00545 } 00546 00547 00548 // ----------------------------------------------------------------- 00549 std::string 00550 Url::getQueryString() const 00551 { 00552 return m_impl->getQueryString(); 00553 } 00554 00555 00556 // ----------------------------------------------------------------- 00557 std::string 00558 Url::getFragment(zypp::url::EEncoding eflag) const 00559 { 00560 return m_impl->getFragment(eflag); 00561 } 00562 00563 00564 // ----------------------------------------------------------------- 00565 std::string 00566 Url::getUsername(EEncoding eflag) const 00567 { 00568 return m_impl->getUsername(eflag); 00569 } 00570 00571 00572 // ----------------------------------------------------------------- 00573 std::string 00574 Url::getPassword(EEncoding eflag) const 00575 { 00576 return m_impl->getPassword(eflag); 00577 } 00578 00579 00580 // ----------------------------------------------------------------- 00581 std::string 00582 Url::getHost(EEncoding eflag) const 00583 { 00584 return m_impl->getHost(eflag); 00585 } 00586 00587 00588 // ----------------------------------------------------------------- 00589 std::string 00590 Url::getPort() const 00591 { 00592 return m_impl->getPort(); 00593 } 00594 00595 00596 // ----------------------------------------------------------------- 00597 std::string 00598 Url::getPathName(EEncoding eflag) const 00599 { 00600 return m_impl->getPathName(eflag); 00601 } 00602 00603 00604 // ----------------------------------------------------------------- 00605 std::string 00606 Url::getPathParams() const 00607 { 00608 return m_impl->getPathParams(); 00609 } 00610 00611 00612 // ----------------------------------------------------------------- 00613 zypp::url::ParamVec 00614 Url::getPathParamsVec() const 00615 { 00616 return m_impl->getPathParamsVec(); 00617 } 00618 00619 00620 // ----------------------------------------------------------------- 00621 zypp::url::ParamMap 00622 Url::getPathParamsMap(EEncoding eflag) const 00623 { 00624 return m_impl->getPathParamsMap(eflag); 00625 } 00626 00627 00628 // ----------------------------------------------------------------- 00629 std::string 00630 Url::getPathParam(const std::string ¶m, EEncoding eflag) const 00631 { 00632 return m_impl->getPathParam(param, eflag); 00633 } 00634 00635 00636 // ----------------------------------------------------------------- 00637 zypp::url::ParamVec 00638 Url::getQueryStringVec() const 00639 { 00640 return m_impl->getQueryStringVec(); 00641 } 00642 00643 00644 // ----------------------------------------------------------------- 00645 zypp::url::ParamMap 00646 Url::getQueryStringMap(EEncoding eflag) const 00647 { 00648 return m_impl->getQueryStringMap(eflag); 00649 } 00650 00651 00652 // ----------------------------------------------------------------- 00653 std::string 00654 Url::getQueryParam(const std::string ¶m, EEncoding eflag) const 00655 { 00656 return m_impl->getQueryParam(param, eflag); 00657 } 00658 00659 00660 // ----------------------------------------------------------------- 00661 void 00662 Url::setScheme(const std::string &scheme) 00663 { 00664 if(scheme == m_impl->getScheme()) 00665 { 00666 return; 00667 } 00668 if( m_impl->isKnownScheme(scheme)) 00669 { 00670 m_impl->setScheme(scheme); 00671 return; 00672 } 00673 00674 UrlRef url = g_urlSchemeRepository().getUrlByScheme(scheme); 00675 if( !url) 00676 { 00677 url.reset( new UrlBase()); 00678 } 00679 url->init( 00680 scheme, 00681 m_impl->getAuthority(), 00682 m_impl->getPathData(), 00683 m_impl->getQueryString(), 00684 m_impl->getFragment(zypp::url::E_ENCODED) 00685 ); 00686 m_impl = url; 00687 } 00688 00689 00690 // ----------------------------------------------------------------- 00691 void 00692 Url::setAuthority(const std::string &authority) 00693 { 00694 m_impl->setAuthority(authority); 00695 } 00696 00697 00698 // ----------------------------------------------------------------- 00699 void 00700 Url::setPathData(const std::string &pathdata) 00701 { 00702 m_impl->setPathData(pathdata); 00703 } 00704 00705 00706 // ----------------------------------------------------------------- 00707 void 00708 Url::setQueryString(const std::string &querystr) 00709 { 00710 m_impl->setQueryString(querystr); 00711 } 00712 00713 00714 // ----------------------------------------------------------------- 00715 void 00716 Url::setFragment(const std::string &fragment, EEncoding eflag) 00717 { 00718 m_impl->setFragment(fragment, eflag); 00719 } 00720 00721 00722 // ----------------------------------------------------------------- 00723 void 00724 Url::setUsername(const std::string &user, 00725 EEncoding eflag) 00726 { 00727 m_impl->setUsername(user, eflag); 00728 } 00729 00730 00731 // ----------------------------------------------------------------- 00732 void 00733 Url::setPassword(const std::string &pass, 00734 EEncoding eflag) 00735 { 00736 m_impl->setPassword(pass, eflag); 00737 } 00738 00739 00740 // ----------------------------------------------------------------- 00741 void 00742 Url::setHost(const std::string &host) 00743 { 00744 m_impl->setHost(host); 00745 } 00746 00747 00748 // ----------------------------------------------------------------- 00749 void 00750 Url::setPort(const std::string &port) 00751 { 00752 m_impl->setPort(port); 00753 } 00754 00755 00756 // ----------------------------------------------------------------- 00757 void 00758 Url::setPathName(const std::string &path, 00759 EEncoding eflag) 00760 { 00761 m_impl->setPathName(path, eflag); 00762 } 00763 00764 void 00765 Url::setPathName(const Pathname &path, 00766 EEncoding eflag) 00767 { 00768 m_impl->setPathName(path.asString(), eflag); 00769 } 00770 00771 void 00772 Url::setPathName(const char *path, 00773 EEncoding eflag) 00774 { 00775 m_impl->setPathName(path, eflag); 00776 } 00777 00778 // ----------------------------------------------------------------- 00779 void 00780 Url::setPathParams(const std::string ¶ms) 00781 { 00782 m_impl->setPathParams(params); 00783 } 00784 00785 00786 // ----------------------------------------------------------------- 00787 void 00788 Url::setPathParamsVec(const zypp::url::ParamVec &pvec) 00789 { 00790 m_impl->setPathParamsVec(pvec); 00791 } 00792 00793 00794 // ----------------------------------------------------------------- 00795 void 00796 Url::setPathParamsMap(const zypp::url::ParamMap &pmap) 00797 { 00798 m_impl->setPathParamsMap(pmap); 00799 } 00800 00801 00802 // ----------------------------------------------------------------- 00803 void 00804 Url::setPathParam(const std::string ¶m, const std::string &value) 00805 { 00806 m_impl->setPathParam(param, value); 00807 } 00808 00809 00810 // ----------------------------------------------------------------- 00811 void 00812 Url::setQueryStringVec(const zypp::url::ParamVec &pvec) 00813 { 00814 m_impl->setQueryStringVec(pvec); 00815 } 00816 00817 00818 // ----------------------------------------------------------------- 00819 void 00820 Url::setQueryStringMap(const zypp::url::ParamMap &pmap) 00821 { 00822 m_impl->setQueryStringMap(pmap); 00823 } 00824 00825 // ----------------------------------------------------------------- 00826 void 00827 Url::setQueryParam(const std::string ¶m, const std::string &value) 00828 { 00829 m_impl->setQueryParam(param, value); 00830 } 00831 00832 // ----------------------------------------------------------------- 00833 void 00834 Url::delQueryParam(const std::string ¶m) 00835 { 00836 m_impl->delQueryParam(param); 00837 } 00838 00839 // ----------------------------------------------------------------- 00840 ViewOptions 00841 Url::getViewOptions() const 00842 { 00843 return m_impl->getViewOptions(); 00844 } 00845 00846 // ----------------------------------------------------------------- 00847 void 00848 Url::setViewOptions(const ViewOptions &vopts) 00849 { 00850 m_impl->setViewOptions(vopts); 00851 } 00852 00853 // ----------------------------------------------------------------- 00854 std::ostream & operator<<( std::ostream & str, const Url & url ) 00855 { 00856 return str << url.asString(); 00857 } 00858 00859 bool operator<( const Url &lhs, const Url &rhs ) 00860 { 00861 return (lhs.asCompleteString() < rhs.asCompleteString()); 00862 } 00863 00864 bool operator==( const Url &lhs, const Url &rhs ) 00865 { 00866 return (lhs.asCompleteString() == rhs.asCompleteString()); 00867 } 00868 00869 bool operator!=( const Url &lhs, const Url &rhs ) 00870 { 00871 return (lhs.asCompleteString() != rhs.asCompleteString()); 00872 } 00873 00875 } // namespace zypp 00877 /* 00878 ** vim: set ts=2 sts=2 sw=2 ai et: 00879 */