/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "sourceprovider-scanner.hxx" #include "sourcetreeprovider.hxx" #if defined MACOSX #include #include #endif namespace unoidl::detail { namespace { //TODO: Bad hack to work around osl::FileStatus::getFileName not determining the // original spelling of a file name (not even with // osl_FileStatus_Mask_Validate): OUString getFileName(OUString const & uri, osl::FileStatus const & status) { #if defined MACOSX sal_Int32 i = uri.lastIndexOf('/') + 1; OUString path; if (osl::FileBase::getSystemPathFromFileURL(uri.copy(0, i), path) != osl::FileBase::E_None) { SAL_WARN( "unoidl", "cannot getSystemPathFromFileURL(" << uri.copy(0, i) << ")"); return status.getFileName(); } OString dir(OUStringToOString(path, osl_getThreadTextEncoding())); OString name(OUStringToOString(uri.subView(i), osl_getThreadTextEncoding())); DIR * d = opendir(dir.getStr()); if (d == nullptr) { SAL_WARN("unoidl", "cannot opendir(" << dir << ")"); return status.getFileName(); } for (;;) { dirent ent; dirent * p; int e = readdir_r(d, &ent, &p); if (e != 0) { SAL_WARN("unoidl", "cannot readdir_r"); closedir(d); return status.getFileName(); } if (p == nullptr) { SAL_WARN( "unoidl", "cannot find " << name << " via readdir of " << dir); closedir(d); return status.getFileName(); } if (name.equalsIgnoreAsciiCase(p->d_name)) { closedir(d); return OUString( p->d_name, std::strlen(p->d_name), osl_getThreadTextEncoding()); } } #else (void) uri; return status.getFileName(); #endif } bool exists(OUString const & uri, bool directory) { osl::DirectoryItem item; osl::FileStatus status( osl_FileStatus_Mask_Type | osl_FileStatus_Mask_FileName); return osl::DirectoryItem::get(uri, item) == osl::FileBase::E_None && item.getFileStatus(status) == osl::FileBase::E_None && (status.getFileType() == osl::FileStatus::Directory) == directory && getFileName(uri, status) == uri.subView(uri.lastIndexOf('/') + 1); } class Cursor: public MapCursor { public: Cursor(Manager& manager, OUString const & uri): manager_(manager), directory_(uri) { auto const rc = directory_.open(); SAL_WARN_IF( rc != osl::FileBase::E_None, "unoidl", "open(" << uri << ") failed with " << +rc); } private: virtual ~Cursor() noexcept override {} virtual rtl::Reference getNext(OUString *) override; Manager& manager_; osl::Directory directory_; }; class SourceModuleEntity: public ModuleEntity { public: SourceModuleEntity(Manager& manager, OUString uri): manager_(manager), uri_(std::move(uri)) {} private: virtual ~SourceModuleEntity() noexcept override {} virtual std::vector getMemberNames() const override { return std::vector(); } //TODO virtual rtl::Reference< MapCursor > createCursor() const override { return new Cursor(manager_, uri_); } Manager& manager_; OUString uri_; }; bool isValidFileName(std::u16string_view name, bool directory) { for (size_t i = 0;; ++i) { if (i == name.size()) { if (i == 0) { return false; } return directory; } auto const c = name[i]; if (c == '.') { if (i == 0 || name[i - 1] == '_') { return false; } return !directory && name.substr(i + 1) == u"idl"; } else if (c == '_') { //TODO: Ignore case of name[0] only for case-insensitive file systems: if (i == 0 || name[i - 1] == '_') { return false; } } else if (rtl::isAsciiDigit(c)) { if (i == 0) { return false; } } else if (!rtl::isAsciiAlpha(c)) { return false; } } } } rtl::Reference Cursor::getNext(OUString * name) { assert(name != nullptr); for (;;) { osl::DirectoryItem i; auto rc = directory_.getNextItem(i); switch (rc) { case osl::FileBase::E_None: { osl::FileStatus stat( osl_FileStatus_Mask_Type | osl_FileStatus_Mask_FileName | osl_FileStatus_Mask_FileURL); rc = i.getFileStatus(stat); if (rc != osl::FileBase::E_None) { SAL_WARN( "unoidl", "getFileSatus in <" << directory_.getURL() << "> failed with " << +rc); continue; } auto const dir = stat.getFileType() == osl::FileStatus::Directory; if (!isValidFileName(stat.getFileName(), dir)) { continue; } if (dir) { //TODO: Using osl::FileStatus::getFileName can likely cause issues on case- // insensitive/preserving file systems, see the free getFileName function above // (which likely goes unnoticed if module identifiers follow the convention of // being all-lowercase): *name = stat.getFileName(); return new SourceModuleEntity(manager_, stat.getFileURL()); } else { SourceProviderScannerData data(&manager_); if (!parse(stat.getFileURL(), &data)) { SAL_WARN("unoidl", "cannot parse <" << stat.getFileURL() << ">"); continue; } auto ent = data.entities.end(); for (auto j = data.entities.begin(); j != data.entities.end(); ++j) { if (j->second.kind == SourceProviderEntity::KIND_EXTERNAL || j->second.kind == SourceProviderEntity::KIND_MODULE) { continue; } if (ent != data.entities.end()) { throw FileFormatException( stat.getFileURL(), "source file defines more than one entity"); } ent = j; } if (ent == data.entities.end()) { throw FileFormatException( stat.getFileURL(), "source file defines no entity"); } //TODO: Check that the entity's name matches the suffix of stat.getFileURL(): *name = ent->first.copy(ent->first.lastIndexOf('.') + 1); return ent->second.entity; } } default: SAL_WARN( "unoidl", "getNext from <" << directory_.getURL() << "> failed with " << +rc); [[fallthrough]]; case osl::FileBase::E_NOENT: return {}; } } } SourceTreeProvider::SourceTreeProvider(Manager & manager, OUString const & uri): manager_(manager), uri_(uri.endsWith("/") ? uri : uri + "/") {} rtl::Reference SourceTreeProvider::createRootCursor() const { return new Cursor(manager_, uri_); } rtl::Reference SourceTreeProvider::findEntity(OUString const & name) const { std::map< OUString, rtl::Reference >::iterator ci( cache_.find(name)); if (ci != cache_.end()) { return ci->second; } // Match name against // name ::= identifier ("." identifier)* // identifier ::= upper-blocks | lower-block // upper-blocks ::= upper ("_"? alnum)* // lower-block :== lower alnum* // alnum ::= digit | upper | lower // digit ::= "0"--"9" // upper ::= "A"--"Z" // lower ::= "a"--"z" OUStringBuffer buf(name); sal_Int32 start = 0; sal_Int32 i = 0; for (; i != name.getLength(); ++i) { sal_Unicode c = name[i]; if (c == '.') { assert(i == start || i != 0); if (i == start || name[i - 1] == '_') { throw FileFormatException( //TODO "", "Illegal UNOIDL identifier \"" + name + "\""); } buf[i] = '/'; start = i + 1; } else if (c == '_') { assert(i == start || i != 0); if (i == start || name[i - 1] == '_' || !rtl::isAsciiUpperCase(name[start])) { throw FileFormatException( //TODO "", "Illegal UNOIDL identifier \"" + name + "\""); } } else if (rtl::isAsciiDigit(c)) { if (i == start) { throw FileFormatException( //TODO "", "Illegal UNOIDL identifier \"" + name + "\""); } } else if (!rtl::isAsciiAlpha(c)) { throw FileFormatException( //TODO "", "Illegal UNOIDL identifier \"" + name + "\""); } } if (i == start) { throw FileFormatException( //TODO "", "Illegal UNOIDL identifier \"" + name + "\""); } OUString uri(uri_ + buf); rtl::Reference ent; // Prevent conflicts between foo/ and Foo.idl on case-preserving file // systems: if (exists(uri, true) && !exists(uri + ".idl", false)) { ent = new SourceModuleEntity(manager_, uri); } else { uri += ".idl"; SourceProviderScannerData data(&manager_); if (parse(uri, &data)) { std::map::const_iterator j( data.entities.find(name)); if (j != data.entities.end()) { ent = j->second.entity; } SAL_WARN_IF( !ent.is(), "unoidl", "<" << uri << "> does not define entity " << name); } } cache_.emplace(name, ent); return ent; } SourceTreeProvider::~SourceTreeProvider() noexcept {} } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */