/* Copyright (C) 2003-2015 LiveCode Ltd.
This file is part of LiveCode.
LiveCode is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License v3 as published by the Free
Software Foundation.
LiveCode is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License
along with LiveCode. If not see . */
////////////////////////////////////////////////////////////////////////////////
//
// Private Source File:
// deploy.cpp
//
// Description:
// This file contains common methods for use by the various deploy modules.
// In particular, it contains the implementation of the MCDeployFile
// abstraction, and implementation of the ide 'deploy' command.
//
// Changes:
// 2009-06-20 MW Created.
// 2009-06-22 MW Committed first working version.
// 2009-06-23 MW Refactored common project writing code to here.
// 2009-06-24 MW Expanded MCDeployWriteProject to allow creation of more
// robust project info suitable for both revlets and exes.
// 2009-06-25 MW Added error catching to the deploy command.
// Fixed glitch in md5_append_file to make sure stream begins
// at start.
// 2009-06-26 MW Fixed error in MCDeployCompress* methods causing wrong data
// to be used (buffer overwritten in wrong place).
// 2009-06-29 MW Added in native path conversion to file paths in deploy.
// Added implementation of MCDeployCatch - used to get the last
// error thrown by a deploy method.
// Added MCDeployErrorToString - used to get a human-readable
// account of a deploy error.
// Added setting of 'version_info' member of params to enable
// creation of VERSIONINFO resource on Windows.
// 2009-06-30 MW Restructed projectinfo output to use new extensible section
// based format.
// 2009-07-01 MW Added check for media edition in deploy command.
// 2009-07-04 MW Rewrote project generation portion to use new capsule
// abstraction.
// 2009-07-07 MW Added support for manifest embedding on Windows (currently
// Enterprise only).
// Added support spill file creation.
// 2009-07-12 MW Added support for _internal sign command for Windows
// Authenticode signing.
// 2010-05-08 MW Added support for payload option when building installers.
// Adjusted icon and manifest option fetching to use filepath
// routines.
// 2010-09-06 MW Removed edition restrictions for switch to LiveCode.
//
////////////////////////////////////////////////////////////////////////////////
#include "prefix.h"
#include "globdefs.h"
#include "objdefs.h"
#include "parsedef.h"
#include "filedefs.h"
#include "exec.h"
#include "handler.h"
#include "scriptpt.h"
#include "variable.h"
#include "statemnt.h"
#include "globals.h"
#include "param.h"
#include "dispatch.h"
#include "osspec.h"
#include "ide.h"
#include "deploy.h"
#include "mode.h"
#include "license.h"
#include "stacksecurity.h"
#include "debug.h"
#include "capsule.h"
////////////////////////////////////////////////////////////////////////////////
extern Boolean InitSSLCrypt(void);
////////////////////////////////////////////////////////////////////////////////
static const char *kMCDeployArchitectureStrings[] =
{
"",
"i386",
"x86-64",
"armv6",
"armv7",
"armv7s",
"arm64",
"ppc",
"ppc64",
nil,
};
typedef struct
{
MCDeployParameters* params;
MCExecContext* ctxt;
}
MCDeployArrayApplyCallbackContext;
static bool MCDeployMapArchitectureString(MCStringRef p_string, MCDeployArchitecture& r_architecture)
{
for(uindex_t i = 0; kMCDeployArchitectureStrings[i] != nil; i++)
{
// As 'p_string' is an MCString the '==' operator does a caseless comparison.
if (MCStringIsEqualToCString(p_string, kMCDeployArchitectureStrings[i], kMCStringOptionCompareCaseless))
{
r_architecture = (MCDeployArchitecture)i;
return true;
}
}
return false;
}
static bool MCDeployPushMinOSVersion(MCDeployParameters* p_params, MCDeployArchitecture p_arch, MCStringRef p_vers_string)
{
// Use sscanf to parse out the version string. We don't check the return value of
// sscanf as we don't care - any malformed / missing components will come out as
// 0.
int t_major, t_minor, t_inc;
t_major = t_minor = t_inc = 0;
MCAutoPointer t_native_string;
MCStringConvertToCString(p_vers_string, &t_native_string);
sscanf(*t_native_string, "%d.%d.%d", &t_major, &t_minor, &t_inc);
if (!MCMemoryResizeArray(p_params -> min_os_version_count + 1, p_params -> min_os_versions, p_params -> min_os_version_count))
return false;
uint32_t t_version;
t_version = (t_major & 0xFFFF) min_os_versions[p_params -> min_os_version_count - 1] . version = t_version;
return true;
}
static bool MCDeployGetArchitectures(void *context, MCArrayRef array, MCNameRef key, MCValueRef value)
{
MCDeployArrayApplyCallbackContext *t_context;
t_context = (MCDeployArrayApplyCallbackContext*)context;
MCAutoStringRef t_value_as_string;
MCDeployArchitecture t_arch;
if (!t_context -> ctxt -> ConvertToString(value, &t_value_as_string))
return false;
if (!MCDeployMapArchitectureString(MCNameGetString(key), t_arch))
return false;
if (!MCDeployPushMinOSVersion(t_context -> params, t_arch, *t_value_as_string))
return false;
return true;
}
bool MCDeployParameters::InitWithArray(MCExecContext &ctxt, MCArrayRef p_array)
{
MCStringRef t_temp_string;
MCArrayRef t_temp_array;
if (!ctxt.CopyOptElementAsFilepath(p_array, MCNAME("engine_ppc"), false, t_temp_string))
return false;
MCValueAssign(engine_ppc, t_temp_string);
MCValueRelease(t_temp_string);
if (!ctxt.CopyOptElementAsFilepath(p_array, MCNAME("engine_x86"), false, t_temp_string))
return false;
MCValueAssign(engine_x86, t_temp_string);
MCValueRelease(t_temp_string);
if (MCStringIsEmpty(engine_ppc) && MCStringIsEmpty(engine_x86))
{
if (!ctxt.CopyElementAsFilepath(p_array, MCNAME("engine"), false, t_temp_string))
return false;
MCValueAssign(engine, t_temp_string);
MCValueRelease(t_temp_string);
}
if (!ctxt.CopyOptElementAsFilepath(p_array, MCNAME("stackfile"), false, t_temp_string))
return false;
MCValueAssign(stackfile, t_temp_string);
MCValueRelease(t_temp_string);
if (!ctxt.CopyOptElementAsFilepathArray(p_array, MCNAME("auxiliary_stackfiles"), false, t_temp_array))
return false;
MCValueAssign(auxiliary_stackfiles, t_temp_array);
MCValueRelease(t_temp_array);
// The externals listed by the IDE are LF separated
if (!ctxt.CopyOptElementAsString(p_array, MCNAME("externals"), false, t_temp_string))
return false;
MCStringSplit(t_temp_string, MCSTR("\n"), nil, kMCStringOptionCompareExact, t_temp_array);
MCValueAssign(externals, t_temp_array);
MCValueRelease(t_temp_string);
MCValueRelease(t_temp_array);
if (!ctxt.CopyOptElementAsString(p_array, MCNAME("startup_script"), false, t_temp_string))
return false;
MCValueAssign(startup_script, t_temp_string);
MCValueRelease(t_temp_string);
// The redirects listed by the IDE are LF separated
if (!ctxt.CopyOptElementAsString(p_array, MCNAME("redirects"), false, t_temp_string))
return false;
MCStringSplit(t_temp_string, MCSTR("\n"), nil, kMCStringOptionCompareExact, t_temp_array);
MCValueAssign(redirects, t_temp_array);
MCValueRelease(t_temp_string);
MCValueRelease(t_temp_array);
if (!ctxt.CopyOptElementAsFilepath(p_array, MCNAME("appicon"), false, t_temp_string))
return false;
MCValueAssign(app_icon, t_temp_string);
MCValueRelease(t_temp_string);
if (!ctxt.CopyOptElementAsFilepath(p_array, MCNAME("docicon"), false, t_temp_string))
return false;
MCValueAssign(doc_icon, t_temp_string);
MCValueRelease(t_temp_string);
if (!ctxt.CopyOptElementAsFilepath(p_array, MCNAME("manifest"), false, t_temp_string))
return false;
MCValueAssign(manifest, t_temp_string);
MCValueRelease(t_temp_string);
if (!ctxt.CopyOptElementAsFilepath(p_array, MCNAME("payload"), false, t_temp_string))
return false;
MCValueAssign(payload, t_temp_string);
MCValueRelease(t_temp_string);
if (!ctxt.CopyOptElementAsFilepath(p_array, MCNAME("spill"), false, t_temp_string))
return false;
MCValueAssign(spill, t_temp_string);
MCValueRelease(t_temp_string);
if (!ctxt.CopyElementAsFilepath(p_array, MCNAME("output"), false, t_temp_string))
return false;
MCValueAssign(output, t_temp_string);
MCValueRelease(t_temp_string);
if (!ctxt.CopyOptElementAsArray(p_array, MCNAME("version"), false, t_temp_array))
return false;
MCValueAssign(version_info, t_temp_array);
MCValueRelease(t_temp_array);
// AL-2015-02-10: [[ Standalone Inclusions ]] Fetch the resource mappings, if any.
if (!ctxt.CopyOptElementAsString(p_array, MCNAME("library"), false, t_temp_string))
return false;
MCStringSplit(t_temp_string, MCSTR("\n"), nil, kMCStringOptionCompareExact, t_temp_array);
MCValueAssign(library, t_temp_array);
MCValueRelease(t_temp_string);
MCValueRelease(t_temp_array);
// SN-2015-02-16: [[ iOS Font mapping ]] Read the fontmappings options from the deploy parameters.
if (!ctxt.CopyOptElementAsString(p_array, MCNAME("fontmappings"), false, t_temp_string))
return false;
MCStringSplit(t_temp_string, MCSTR("\n"), nil, kMCStringOptionCompareExact, t_temp_array);
MCValueAssign(fontmappings, t_temp_array);
MCValueRelease(t_temp_string);
MCValueRelease(t_temp_array);
// The 'min_os_version' is either a string or an array. If it is a string then
// it encodes the version against the 'Unknown' architecture which is interpreted
// by the deploy command to mean all architectures. Otherwise, the keys in the
// array are assumed to be architecture names and each is pushed on the array.
// If the 'min_os_version' is empty, then no change is brought to the binaries.
// If multiple entries are present, then the 'unknown' mapping is used for any
// architecture not explicitly specified. The current architecture strings that are
// known are:
// i386, x86-64, armv6, armv7, armv7s, arm64, ppc, ppc64
// The empty string is taken to be 'unknown'.
if (!ctxt . CopyOptElementAsArray(p_array, MCNAME("min_os_version"), false, t_temp_array))
return false;
// SN-2015-02-04: [[ Merge-6.7.2 ]] If the array is empty, try to convert to a string.
if (!MCArrayIsEmpty(t_temp_array))
{
MCDeployArrayApplyCallbackContext t_context;
t_context . ctxt = &ctxt;
t_context . params = this;
bool t_success;
t_success = MCArrayApply(t_temp_array, MCDeployGetArchitectures, (void*)&t_context);
MCValueRelease(t_temp_array);
if (!t_success)
return false;
}
else
{
MCValueRelease(t_temp_array);
if (!ctxt . CopyOptElementAsString(p_array, MCNAME("min_os_version"), false, t_temp_string))
return false;
if (!MCStringIsEmpty(t_temp_string))
MCDeployPushMinOSVersion(this, kMCDeployArchitecture_Unknown, t_temp_string);
MCValueRelease(t_temp_string);
}
if (!ctxt.CopyOptElementAsFilepathArray(p_array, MCNAME("modules"), false, t_temp_array))
return false;
MCValueAssign(modules, t_temp_array);
MCValueRelease(t_temp_array);
MCAutoStringRef t_architectures_string;
if (!ctxt.CopyOptElementAsString(p_array, MCNAME("architectures"), false, &t_architectures_string))
return false;
if (!MCStringIsEmpty(*t_architectures_string))
{
// Split the string up into items
MCAutoProperListRef t_architectures;
if (!MCStringSplitByDelimiter(*t_architectures_string, MCSTR(","), kMCStringOptionCompareExact, &t_architectures))
return false;
// Process the architectures
MCValueRef t_architecture;
for (uindex_t i = 0; i < MCProperListGetLength(*t_architectures); i++)
{
// Fetch this item and make sure it is a string
t_architecture = MCProperListFetchElementAtIndex(*t_architectures, i);
if (t_architecture == nil || MCValueGetTypeCode(t_architecture) != kMCValueTypeCodeString)
return false;
// Map it to an architecture ID
MCDeployArchitecture t_id;
if (!MCDeployMapArchitectureString((MCStringRef)t_architecture, t_id))
return false;
// Append it to the list of desired architectures
if (!architectures.Push(t_id))
return false;
}
}
// If the 'banner_class' string is present, then set the class override.
MCAutoStringRef t_banner_class;
if (!ctxt . CopyOptElementAsString(p_array, MCNAME("banner_class"), false, &t_banner_class))
return false;
if (MCStringIsEqualToCString(*t_banner_class,
"commercial",
kMCStringOptionCompareCaseless))
banner_class = kMCLicenseClassEvaluation;
else if (MCStringIsEqualToCString(*t_banner_class,
"professional",
kMCStringOptionCompareCaseless))
banner_class = kMCLicenseClassProfessionalEvaluation;
if (!ctxt.CopyOptElementAsString(p_array, MCNAME("uuid"), false, t_temp_string))
return false;
MCValueAssign(uuid, t_temp_string);
MCValueRelease(t_temp_string);
return true;
}
////////////////////////////////////////////////////////////////////////////////
static bool MCDeployWriteDefinePrologueSection(const MCDeployParameters& p_params, MCDeployCapsuleRef p_capsule)
{
MCCapsulePrologueSection t_prologue;
t_prologue . banner_timeout = p_params . banner_timeout;
t_prologue . program_timeout = p_params . timeout;
MCDeployByteSwapRecord(true, "ll", &t_prologue, sizeof(t_prologue));
return MCDeployCapsuleDefine(p_capsule, kMCCapsuleSectionTypePrologue, &t_prologue, sizeof(t_prologue));
}
static bool MCDeployWriteDefineLicenseSection(const MCDeployParameters& p_params, MCDeployCapsuleRef p_capsule)
{
bool t_success = true;
IO_handle t_stream_handle;
t_stream_handle = nullptr;
if (t_success)
{
t_stream_handle = MCS_fakeopenwrite();
t_success = t_stream_handle != nullptr;
}
if (t_success)
{
t_success = IO_write_uint1((uint1)MClicenseparameters . license_class, t_stream_handle) == IO_NORMAL;
}
if (t_success && MClicenseparameters . addons != nullptr)
{
t_success = IO_write_valueref_new(MClicenseparameters . addons, t_stream_handle) == IO_NORMAL;
}
//////////
void *t_buffer;
size_t t_length = 0;
t_buffer = nullptr;
if (t_success)
{
t_success = MCS_closetakingbuffer(t_stream_handle, t_buffer, t_length) == IO_NORMAL;
}
if (t_success)
{
t_success = MCDeployCapsuleDefine(p_capsule,
kMCCapsuleSectionTypeLicense,
t_buffer,
uint32_t(t_length));
}
if (t_buffer != nullptr)
{
free(t_buffer);
}
return t_success;
}
static bool MCDeployWriteDefineBannerSecion(const MCDeployParameters& p_params, MCDeployCapsuleRef p_capsule)
{
return MCDeployCapsuleDefine(p_capsule, kMCCapsuleSectionTypeBanner, MCDataGetBytePtr(p_params . banner_stackfile), MCDataGetLength(p_params . banner_stackfile));
}
// This method generates the standalone specific capsule elements. This is
// just a Standalone Prologue section at the moment.
static bool MCDeployWriteCapsuleDefineStandaloneSections(const MCDeployParameters& p_params, MCDeployCapsuleRef p_capsule)
{
bool t_success;
t_success = true;
// First emit the prologue.
if (t_success)
t_success = MCDeployWriteDefinePrologueSection(p_params, p_capsule);
// Next emit the license info.
if (t_success)
t_success = MCDeployWriteDefineLicenseSection(p_params, p_capsule);
// Next emit the banner.
if (t_success &&
!MCDataIsEmpty(p_params . banner_stackfile))
t_success = MCDeployWriteDefineBannerSecion(p_params, p_capsule);
return t_success;
}
static bool MCDeployCapsuleDefineFromStackFile(MCDeployCapsuleRef p_self, MCStringRef p_filename, MCDeployFileRef p_file, bool p_mainstack)
{
MCAutoDataRef t_contents;
if (!MCS_loadbinaryfile(p_filename, &t_contents))
return false;
IO_handle t_stream = nil;
t_stream = MCS_fakeopen(MCDataGetBytePtr(*t_contents),MCDataGetLength(*t_contents));
if (t_stream == nil)
return false;
bool t_script_only = MCdispatcher -> streamstackisscriptonly(t_stream);
MCS_close(t_stream);
MCCapsuleSectionType t_type;
if (p_mainstack)
{
if (t_script_only)
{
t_type = kMCCapsuleSectionTypeScriptOnlyMainStack;
}
else
{
t_type = kMCCapsuleSectionTypeMainStack;
}
}
else
{
if (t_script_only)
{
t_type = kMCCapsuleSectionTypeScriptOnlyAuxiliaryStack;
}
else
{
t_type = kMCCapsuleSectionTypeAuxiliaryStack;
}
}
return MCDeployCapsuleDefineFromFile(p_self, t_type, p_file);
}
// This method constructs and then writes out a capsule to the given output file.
// The capsule contents is derived from the deploy parameters structure.
// The offset in the file after writing is returned in x_offset.
bool MCDeployWriteCapsule(const MCDeployParameters& p_params, MCDeployFileRef p_output, uint32_t& x_offset)
{
bool t_success;
t_success = true;
// Open the stackfile.
MCDeployFileRef t_stackfile;
t_stackfile = NULL;
if (t_success && !MCDeployFileOpen(p_params . stackfile, kMCOpenFileModeRead, t_stackfile))
t_success = MCDeployThrow(kMCDeployErrorNoStackfile);
// Open the spill file, if required
MCDeployFileRef t_spill;
t_spill = NULL;
if (t_success && !MCStringIsEmpty(p_params . spill) && !MCDeployFileOpen(p_params . spill, kMCOpenFileModeCreate, t_spill))
t_success = MCDeployThrow(kMCDeployErrorNoSpill);
// First create our deployment capsule
MCDeployCapsuleRef t_capsule;
t_capsule = nil;
if (t_success)
t_success = MCDeployCapsuleCreate(t_capsule);
// Next, the first thing to do is to add the the header section.
if (t_success)
t_success = MCDeployWriteCapsuleDefineStandaloneSections(p_params, t_capsule);
// Add any redirects
if (t_success)
for(uindex_t i = 0; i < MCArrayGetCount(p_params.redirects) && t_success; i++)
{
MCValueRef t_val;
/* UNCHECKED */ MCArrayFetchValueAtIndex(p_params.redirects, i + 1, t_val);
t_success = MCDeployCapsuleDefineString(t_capsule, kMCCapsuleSectionTypeRedirect, (MCStringRef)t_val);
}
////////
// AL-2015-02-10: [[ Standalone Inclusions ]] Add the resource mappings, if any.
if (t_success)
for(uindex_t i = 0; i < MCArrayGetCount(p_params.library) && t_success; i++)
{
MCValueRef t_val;
/* UNCHECKED */ MCArrayFetchValueAtIndex(p_params.library, i + 1, t_val);
t_success = MCDeployCapsuleDefineString(t_capsule, kMCCapsuleSectionTypeLibrary, (MCStringRef)t_val);
}
////////
// Add all the modules before the stacks, this is so that widgets can resolve
// themselves on load.
MCAutoArray t_module_files;
if (t_success)
t_success = t_module_files . New(MCArrayGetCount(p_params . modules));
if (t_success)
for(uindex_t i = 0; i < MCArrayGetCount(p_params.modules) && t_success; i++)
{
MCValueRef t_module_filename;
/* UNCHECKED */ MCArrayFetchValueAtIndex(p_params .modules, i + 1, t_module_filename);
if (t_success && !MCDeployFileOpen((MCStringRef)t_module_filename, kMCOpenFileModeRead, t_module_files[i]))
t_success = MCDeployThrow(kMCDeployErrorNoModule);
if (t_success)
t_success = MCDeployCapsuleDefineFromFile(t_capsule, kMCCapsuleSectionTypeModule, t_module_files[i]);
}
////////
// Add any font mappings
if (t_success)
for(uint32_t i = 0; i < MCArrayGetCount(p_params.fontmappings) && t_success; i++)
{
MCValueRef t_val;
/* UNCHECKED */ MCArrayFetchValueAtIndex(p_params.fontmappings, i + 1, t_val);
t_success = MCDeployCapsuleDefineString(t_capsule, kMCCapsuleSectionTypeFontmap, (MCStringRef)t_val);
}
// Now we add the main stack
if (t_success)
t_success = MCDeployCapsuleDefineFromStackFile(t_capsule, p_params . stackfile, t_stackfile, true);
// Now we add the auxillary stackfiles, if any
MCAutoArray t_aux_stackfiles;
if (t_success)
t_success = t_aux_stackfiles . New(MCArrayGetCount(p_params . auxiliary_stackfiles));
if (t_success)
for(uindex_t i = 0; i < MCArrayGetCount(p_params.auxiliary_stackfiles) && t_success; i++)
{
MCValueRef t_val;
/* UNCHECKED */ MCArrayFetchValueAtIndex(p_params.auxiliary_stackfiles, i + 1, t_val);
if (t_success && !MCDeployFileOpen((MCStringRef)t_val, kMCOpenFileModeRead, t_aux_stackfiles[i]))
t_success = MCDeployThrow(kMCDeployErrorNoAuxStackfile);
if (t_success)
t_success = MCDeployCapsuleDefineFromStackFile(t_capsule, (MCStringRef)t_val, t_aux_stackfiles[i], false);
}
// Now add the externals, if any
if (t_success)
for(uindex_t i = 0; i < MCArrayGetCount(p_params.externals) && t_success; i++)
{
MCValueRef t_val;
/* UNCHECKED */ MCArrayFetchValueAtIndex(p_params.externals, i + 1, t_val);
t_success = MCDeployCapsuleDefineString(t_capsule, kMCCapsuleSectionTypeExternal, (MCStringRef)t_val);
}
// Now add the startup script, if any.
if (t_success && (!MCStringIsEmpty(p_params . startup_script)))
t_success = MCDeployCapsuleDefineString(t_capsule, kMCCapsuleSectionTypeStartupScript, p_params . startup_script);
// Now a digest
if (t_success)
t_success = MCDeployCapsuleChecksum(t_capsule);
// Finally the epilogue
if (t_success)
t_success = MCDeployCapsuleDefine(t_capsule, kMCCapsuleSectionTypeEpilogue, nil, 0);
// Now we write it
if (t_success)
t_success = MCDeployCapsuleGenerate(t_capsule, p_output, t_spill, x_offset);
MCDeployCapsuleDestroy(t_capsule);
for(uindex_t i = 0; i < t_aux_stackfiles . Size(); i++)
MCDeployFileClose(t_aux_stackfiles[i]);
for(uindex_t i = 0; i < t_module_files . Size(); i++)
MCDeployFileClose(t_module_files[i]);
MCDeployFileClose(t_spill);
MCDeployFileClose(t_stackfile);
return t_success;
}
// This method writes out a project capsule. This consists of a length uint32_t
// followed by the capsule data. The size returned always falls on a 4-byte
// boundary.
bool MCDeployWriteProject(const MCDeployParameters& p_params, bool p_to_network, MCDeployFileRef p_output, uint32_t p_output_offset, uint32_t& r_project_size)
{
bool t_success;
t_success = true;
// A capsule struct is simply a uint32_t followed by the data
uint32_t t_offset;
t_offset = p_output_offset + sizeof(uint32_t);
// First write the capsule to the output file, leaving room for the size field.
// Note that a capsule is always a multiple of four bytes in length, so offset
// will be rounded to a nice value.
if (t_success)
t_success = MCDeployWriteCapsule(p_params, p_output, t_offset);
// Work out the size of the capsule struct (including size field)
uint32_t t_project_size;
if (t_success)
t_project_size = t_offset - p_output_offset;
// Now write out the size field
if (t_success)
{
uint32_t t_swapped_size;
t_swapped_size = t_project_size;
if (!MCStringIsEmpty(p_params . spill))
t_swapped_size |= 1U