#include "bundlediskrep.h"
#include <CoreFoundation/CFURLAccess.h>
#include <CoreFoundation/CFBundlePriv.h>
#include <security_utilities/cfmunge.h>
#include <copyfile.h>
namespace Security {
namespace CodeSigning {
using namespace UnixPlusPlus;
BundleDiskRep::BundleDiskRep(const char *path, const Context *ctx)
: mBundle(_CFBundleCreateIfMightBeBundle(NULL, CFTempURL(path)))
{
if (!mBundle)
MacOSError::throwMe(errSecCSBadObjectFormat);
mExecRep = DiskRep::bestFileGuess(this->mainExecutablePath(), ctx);
CODESIGN_DISKREP_CREATE_BUNDLE_PATH(this, (char*)path, (void*)ctx, mExecRep);
}
BundleDiskRep::BundleDiskRep(CFBundleRef ref, const Context *ctx)
{
mBundle = ref; mExecRep = DiskRep::bestFileGuess(this->mainExecutablePath(), ctx);
CODESIGN_DISKREP_CREATE_BUNDLE_REF(this, ref, (void*)ctx, mExecRep);
}
string BundleDiskRep::metaPath(const char *name)
{
if (mMetaPath.empty()) {
string support = cfString(CFBundleCopySupportFilesDirectoryURL(mBundle), true);
mMetaPath = support + "/" BUNDLEDISKREP_DIRECTORY;
if (::access(mMetaPath.c_str(), F_OK) == 0) {
mMetaExists = true;
} else {
mMetaPath = support;
mMetaExists = false;
}
}
return mMetaPath + "/" + name;
}
void BundleDiskRep::createMeta()
{
string meta = metaPath(BUNDLEDISKREP_DIRECTORY);
if (!mMetaExists) {
if (::mkdir(meta.c_str(), 0755) == 0) {
copyfile(cfString(canonicalPath(), true).c_str(), meta.c_str(), NULL, COPYFILE_SECURITY);
mMetaPath = meta;
mMetaExists = true;
} else if (errno != EEXIST)
UnixError::throwMe();
}
}
CFDataRef BundleDiskRep::component(CodeDirectory::SpecialSlot slot)
{
switch (slot) {
case cdInfoSlot:
if (CFRef<CFURLRef> info = _CFBundleCopyInfoPlistURL(mBundle))
return cfLoadFile(info);
else
return NULL;
default:
if (CFDataRef data = mExecRep->component(slot))
return data;
case cdResourceDirSlot:
if (const char *name = CodeDirectory::canonicalSlotName(slot))
return metaData(name);
else
return NULL;
}
}
CFDataRef BundleDiskRep::identification()
{
return mExecRep->identification();
}
CFURLRef BundleDiskRep::canonicalPath()
{
return CFBundleCopyBundleURL(mBundle);
}
string BundleDiskRep::recommendedIdentifier()
{
if (CFStringRef identifier = CFBundleGetIdentifier(mBundle))
return cfString(identifier);
if (CFDictionaryRef infoDict = CFBundleGetInfoDictionary(mBundle))
if (CFStringRef identifier = CFStringRef(CFDictionaryGetValue(infoDict, kCFBundleNameKey)))
return cfString(identifier);
string path = cfString(this->canonicalPath(), true);
if (path.substr(path.size() - 4) == ".app")
path = path.substr(0, path.size() - 4);
string::size_type p = path.rfind('/');
if (p == string::npos)
return path;
else
return path.substr(p+1);
}
string BundleDiskRep::mainExecutablePath()
{
if (CFURLRef exec = CFBundleCopyExecutableURL(mBundle))
return cfString(exec, true);
else
MacOSError::throwMe(errSecCSBadObjectFormat);
}
string BundleDiskRep::resourcesRootPath()
{
return cfString(CFBundleCopySupportFilesDirectoryURL(mBundle), true);
}
CFDictionaryRef BundleDiskRep::defaultResourceRules()
{
return cfmake<CFDictionaryRef>("{rules={"
"'^version.plist$' = #T"
"'^Resources/' = #T"
"'^Resources/.*\\.lproj/' = {optional=#T, weight=1000}"
"'^Resources/.*\\.lproj/locversion.plist$' = {omit=#T, weight=1100}"
"}}");
}
void BundleDiskRep::adjustResources(ResourceBuilder &builder)
{
builder.addExclusion("^" BUNDLEDISKREP_DIRECTORY "/");
string resources = resourcesRootPath();
string executable = mainExecutablePath();
if (!executable.compare(0, resources.length(), resources, 0, resources.length())) builder.addExclusion(string("^")
+ ResourceBuilder::escapeRE(executable.substr(resources.length() + 1)) + "$");
}
const Requirements *BundleDiskRep::defaultRequirements(const Architecture *arch)
{
return mExecRep->defaultRequirements(arch);
}
Universal *BundleDiskRep::mainExecutableImage()
{
return mExecRep->mainExecutableImage();
}
size_t BundleDiskRep::pageSize()
{
return mExecRep->pageSize();
}
size_t BundleDiskRep::signingBase()
{
return mExecRep->signingBase();
}
size_t BundleDiskRep::signingLimit()
{
return mExecRep->signingLimit();
}
string BundleDiskRep::format()
{
return string("bundle with ") + mExecRep->format();
}
CFArrayRef BundleDiskRep::modifiedFiles()
{
CFMutableArrayRef files = CFArrayCreateMutableCopy(NULL, 0, mExecRep->modifiedFiles());
checkModifiedFile(files, cdCodeDirectorySlot);
checkModifiedFile(files, cdSignatureSlot);
checkModifiedFile(files, cdResourceDirSlot);
checkModifiedFile(files, cdEntitlementSlot);
return files;
}
void BundleDiskRep::checkModifiedFile(CFMutableArrayRef files, CodeDirectory::SpecialSlot slot)
{
if (CFDataRef data = mExecRep->component(slot)) CFRelease(data);
else if (const char *resourceName = CodeDirectory::canonicalSlotName(slot)) {
string file = metaPath(resourceName);
if (::access(file.c_str(), F_OK) == 0)
CFArrayAppendValue(files, CFTempURL(file));
}
}
FileDesc &BundleDiskRep::fd()
{
return mExecRep->fd();
}
void BundleDiskRep::flush()
{
mExecRep->flush();
}
DiskRep::Writer *BundleDiskRep::writer()
{
return new Writer(this);
}
BundleDiskRep::Writer::Writer(BundleDiskRep *r)
: rep(r), mMadeMetaDirectory(false)
{
execWriter = rep->mExecRep->writer();
}
void BundleDiskRep::Writer::component(CodeDirectory::SpecialSlot slot, CFDataRef data)
{
switch (slot) {
default:
if (!execWriter->attribute(writerLastResort)) return execWriter->component(slot, data); case cdResourceDirSlot:
if (const char *name = CodeDirectory::canonicalSlotName(slot)) {
rep->createMeta();
string path = rep->metaPath(name);
AutoFileDesc fd(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
fd.writeAll(CFDataGetBytePtr(data), CFDataGetLength(data));
if (rep->mMetaExists) {
string legacy = cfString(CFBundleCopySupportFilesDirectoryURL(rep->mBundle), true) + "/" + name;
# if FORCE_REPLACE_SYMLINK
::unlink(legacy.c_str()); #endif
::symlink((string(BUNDLEDISKREP_DIRECTORY "/") + name).c_str(), legacy.c_str());
}
} else
MacOSError::throwMe(errSecCSBadObjectFormat);
}
}
void BundleDiskRep::Writer::remove()
{
execWriter->remove();
for (CodeDirectory::SpecialSlot slot = 0; slot < cdSlotCount; slot++)
remove(slot);
remove(cdSignatureSlot);
}
void BundleDiskRep::Writer::remove(CodeDirectory::SpecialSlot slot)
{
if (const char *name = CodeDirectory::canonicalSlotName(slot))
if (::unlink(rep->metaPath(name).c_str()))
switch (errno) {
case ENOENT: break;
default:
UnixError::throwMe();
}
}
void BundleDiskRep::Writer::flush()
{
execWriter->flush();
}
} }