/*
- * Copyright (c) 2006-2007 Apple Inc. All Rights Reserved.
+ * Copyright (c) 2006-2011 Apple Inc. All Rights Reserved.
*
* @APPLE_LICENSE_HEADER_START@
*
* @APPLE_LICENSE_HEADER_END@
*/
#include "bundlediskrep.h"
+#include "filediskrep.h"
#include <CoreFoundation/CFURLAccess.h>
#include <CoreFoundation/CFBundlePriv.h>
#include <security_utilities/cfmunge.h>
#include <copyfile.h>
+#include <fts.h>
namespace Security {
using namespace UnixPlusPlus;
+//
+// Local helpers
+//
+static std::string findDistFile(const std::string &directory);
+
+
//
// We make a CFBundleRef immediately, but everything else is lazy
//
BundleDiskRep::BundleDiskRep(const char *path, const Context *ctx)
- : mBundle(_CFBundleCreateIfMightBeBundle(NULL, CFTempURL(path)))
+ : mBundle(CFBundleCreate(NULL, CFTempURL(path)))
{
if (!mBundle)
- MacOSError::throwMe(errSecCSBadObjectFormat);
+ MacOSError::throwMe(errSecCSBadBundleFormat);
setup(ctx);
CODESIGN_DISKREP_CREATE_BUNDLE_PATH(this, (char*)path, (void*)ctx, mExecRep);
}
CODESIGN_DISKREP_CREATE_BUNDLE_REF(this, ref, (void*)ctx, mExecRep);
}
+// common construction code
void BundleDiskRep::setup(const Context *ctx)
{
+ mInstallerPackage = false; // default
+
+ // deal with versioned bundles (aka Frameworks)
string version = resourcesRootPath()
+ "/Versions/"
+ ((ctx && ctx->version) ? ctx->version : "Current")
if (ctx && ctx->version) // explicitly specified
MacOSError::throwMe(errSecCSStaticCodeNotFound);
}
- mExecRep = DiskRep::bestFileGuess(this->mainExecutablePath(), ctx);
+
+ CFDictionaryRef infoDict = CFBundleGetInfoDictionary(mBundle);
+ assert(infoDict); // CFBundle will always make one up for us
+ CFTypeRef mainHTML = CFDictionaryGetValue(infoDict, CFSTR("MainHTML"));
+ CFTypeRef packageVersion = CFDictionaryGetValue(infoDict, CFSTR("IFMajorVersion"));
+
+ // conventional executable bundle: CFBundle identifies an executable for us
+ if (CFRef<CFURLRef> mainExec = CFBundleCopyExecutableURL(mBundle)) // if CFBundle claims an executable...
+ if (mainHTML == NULL) { // ... and it's not a widget
+ mMainExecutableURL = mainExec;
+ mExecRep = DiskRep::bestFileGuess(this->mainExecutablePath(), ctx);
+ mFormat = "bundle with " + mExecRep->format();
+ return;
+ }
+
+ // widget
+ if (mainHTML) {
+ if (CFGetTypeID(mainHTML) != CFStringGetTypeID())
+ MacOSError::throwMe(errSecCSBadBundleFormat);
+ mMainExecutableURL.take(makeCFURL(cfString(CFStringRef(mainHTML)), false,
+ CFRef<CFURLRef>(CFBundleCopySupportFilesDirectoryURL(mBundle))));
+ if (!mMainExecutableURL)
+ MacOSError::throwMe(errSecCSBadBundleFormat);
+ mExecRep = new FileDiskRep(this->mainExecutablePath().c_str());
+ mFormat = "widget bundle";
+ return;
+ }
+
+ // do we have a real Info.plist here?
+ if (CFRef<CFURLRef> infoURL = _CFBundleCopyInfoPlistURL(mBundle)) {
+ // focus on the Info.plist (which we know exists) as the nominal "main executable" file
+ if ((mMainExecutableURL = _CFBundleCopyInfoPlistURL(mBundle))) {
+ mExecRep = new FileDiskRep(this->mainExecutablePath().c_str());
+ if (packageVersion) {
+ mInstallerPackage = true;
+ mFormat = "installer package bundle";
+ } else {
+ mFormat = "bundle";
+ }
+ return;
+ }
+ }
+
+ // we're getting desperate here. Perhaps an oldish-style installer package? Look for a *.dist file
+ std::string distFile = findDistFile(this->resourcesRootPath());
+ if (!distFile.empty()) {
+ mMainExecutableURL = makeCFURL(distFile);
+ mExecRep = new FileDiskRep(this->mainExecutablePath().c_str());
+ mInstallerPackage = true;
+ mFormat = "installer package bundle";
+ return;
+ }
+
+ // this bundle cannot be signed
+ MacOSError::throwMe(errSecCSBadBundleFormat);
+}
+
+
+//
+// Return the full path to the one-and-only file named something.dist in a directory.
+// Return empty string if none; throw an exception if multiple. Do not descend into subdirectories.
+//
+static std::string findDistFile(const std::string &directory)
+{
+ std::string found;
+ char *paths[] = {(char *)directory.c_str(), NULL};
+ FTS *fts = fts_open(paths, FTS_PHYSICAL | FTS_NOCHDIR | FTS_NOSTAT, NULL);
+ bool root = true;
+ while (FTSENT *ent = fts_read(fts)) {
+ switch (ent->fts_info) {
+ case FTS_F:
+ case FTS_NSOK:
+ if (!strcmp(ent->fts_path + ent->fts_pathlen - 5, ".dist")) { // found plain file foo.dist
+ if (found.empty()) // first found
+ found = ent->fts_path;
+ else // multiple *.dist files (bad)
+ MacOSError::throwMe(errSecCSBadBundleFormat);
+ }
+ break;
+ case FTS_D:
+ if (!root)
+ fts_set(fts, ent, FTS_SKIP); // don't descend
+ root = false;
+ break;
+ default:
+ break;
+ }
+ }
+ fts_close(fts);
+ return found;
}
string BundleDiskRep::metaPath(const char *name)
{
if (mMetaPath.empty()) {
- string support = cfString(CFBundleCopySupportFilesDirectoryURL(mBundle), true);
+ string support = cfStringRelease(CFBundleCopySupportFilesDirectoryURL(mBundle));
mMetaPath = support + "/" BUNDLEDISKREP_DIRECTORY;
if (::access(mMetaPath.c_str(), F_OK) == 0) {
mMetaExists = true;
string BundleDiskRep::mainExecutablePath()
{
- if (CFURLRef exec = CFBundleCopyExecutableURL(mBundle))
- return cfString(exec, true);
- else
- MacOSError::throwMe(errSecCSNoMainExecutable);
+ return cfString(mMainExecutableURL);
}
string BundleDiskRep::resourcesRootPath()
{
- return cfString(CFBundleCopySupportFilesDirectoryURL(mBundle), true);
+ return cfStringRelease(CFBundleCopySupportFilesDirectoryURL(mBundle));
}
void BundleDiskRep::adjustResources(ResourceBuilder &builder)
{
// exclude entire contents of meta directory
builder.addExclusion("^" BUNDLEDISKREP_DIRECTORY "/");
+
+ // exclude the store manifest directory
+ builder.addExclusion("^" STORE_RECEIPT_DIRECTORY "/");
// exclude the main executable file
string resources = resourcesRootPath();
string BundleDiskRep::format()
{
- return string("bundle with ") + mExecRep->format();
+ return mFormat;
}
CFArrayRef BundleDiskRep::modifiedFiles()
CFDictionaryRef BundleDiskRep::defaultResourceRules(const SigningContext &)
{
+ // consider the bundle's structure
+ string rbase = this->resourcesRootPath();
+ if (rbase.substr(rbase.length()-2, 2) == "/.") // produced by versioned bundle implicit "Current" case
+ rbase = rbase.substr(0, rbase.length()-2); // ... so take it off for this
+ string resources = cfStringRelease(CFBundleCopyResourcesDirectoryURL(mBundle));
+ if (resources == rbase)
+ resources = "";
+ else if (resources.compare(0, rbase.length(), rbase, 0, rbase.length()) != 0) // Resources not in resource root
+ MacOSError::throwMe(errSecCSBadBundleFormat);
+ else
+ resources = resources.substr(rbase.length() + 1) + "/"; // differential path segment
+
+ // installer package rules
+ if (mInstallerPackage)
+ return cfmake<CFDictionaryRef>("{rules={"
+ "'^.*' = #T" // include everything, but...
+ "%s = {optional=#T, weight=1000}" // make localizations optional
+ "'^.*/.*\\.pkg/' = {omit=#T, weight=10000}" // and exclude all nested packages (by name)
+ "}}",
+ (string("^") + resources + ".*\\.lproj/").c_str()
+ );
+
+ // executable bundle rules
return cfmake<CFDictionaryRef>("{rules={"
- "'^version.plist$' = #T"
- "'^Resources/' = #T"
- "'^Resources/.*\\.lproj/' = {optional=#T, weight=1000}"
- "'^Resources/.*\\.lproj/locversion.plist$' = {omit=#T, weight=1100}"
- "}}");
+ "'^version.plist$' = #T" // include version.plist
+ "%s = #T" // include Resources
+ "%s = {optional=#T, weight=1000}" // make localizations optional
+ "%s = {omit=#T, weight=1100}" // exclude all locversion.plist files
+ "}}",
+ (string("^") + resources).c_str(),
+ (string("^") + resources + ".*\\.lproj/").c_str(),
+ (string("^") + resources + ".*\\.lproj/locversion.plist$").c_str()
+ );
}
const Requirements *BundleDiskRep::defaultRequirements(const Architecture *arch, const SigningContext &ctx)
AutoFileDesc fd(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
fd.writeAll(CFDataGetBytePtr(data), CFDataGetLength(data));
} else
- MacOSError::throwMe(errSecCSBadObjectFormat);
+ MacOSError::throwMe(errSecCSBadBundleFormat);
}
}