Add marshalling metadata lookup & adapt iOS for new APIs.
This commit is contained in:
		@@ -102,7 +102,7 @@ const char *mpw_siteResult(
 | 
			
		||||
                return NULL;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    else if (resultType & MPResultTypeClassState) {
 | 
			
		||||
    else if (resultType & MPResultTypeClassStateful) {
 | 
			
		||||
        switch (algorithmVersion) {
 | 
			
		||||
            case MPAlgorithmVersion0:
 | 
			
		||||
                return mpw_sitePasswordFromCrypt_v0( masterKey, siteKey, resultType, resultParam );
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@
 | 
			
		||||
#include "mpw-marshall-util.h"
 | 
			
		||||
#include "mpw-util.h"
 | 
			
		||||
 | 
			
		||||
char *mpw_get_token(char **in, char *eol, char *delim) {
 | 
			
		||||
char *mpw_get_token(const char **in, const char *eol, char *delim) {
 | 
			
		||||
 | 
			
		||||
    // Skip leading spaces.
 | 
			
		||||
    for (; **in == ' '; ++*in);
 | 
			
		||||
 
 | 
			
		||||
@@ -30,7 +30,7 @@
 | 
			
		||||
  * The input string reference is advanced beyond the token delimitor if one is found.
 | 
			
		||||
  * @return A new string containing the token or NULL if the delim wasn't found before eol. */
 | 
			
		||||
char *mpw_get_token(
 | 
			
		||||
        char **in, char *eol, char *delim);
 | 
			
		||||
        const char **in, const char *eol, char *delim);
 | 
			
		||||
/** Convert an RFC 3339 time string into epoch time. */
 | 
			
		||||
time_t mpw_mktime(
 | 
			
		||||
        const char *time);
 | 
			
		||||
 
 | 
			
		||||
@@ -89,6 +89,20 @@ MPMarshalledQuestion *mpw_marshal_question(
 | 
			
		||||
    return question;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool mpw_marshal_info_free(
 | 
			
		||||
        MPMarshallInfo *info) {
 | 
			
		||||
 | 
			
		||||
    if (!info)
 | 
			
		||||
        return true;
 | 
			
		||||
 | 
			
		||||
    bool success = true;
 | 
			
		||||
    success &= mpw_free_string( info->fullName );
 | 
			
		||||
    success &= mpw_free_string( info->keyID );
 | 
			
		||||
    success &= mpw_free( info, sizeof( MPMarshallInfo ) );
 | 
			
		||||
 | 
			
		||||
    return success;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool mpw_marshal_free(
 | 
			
		||||
        MPMarshalledUser *user) {
 | 
			
		||||
 | 
			
		||||
@@ -313,6 +327,9 @@ bool mpw_marshall_write(
 | 
			
		||||
        char **out, const MPMarshallFormat outFormat, const MPMarshalledUser *user, MPMarshallError *error) {
 | 
			
		||||
 | 
			
		||||
    switch (outFormat) {
 | 
			
		||||
        case MPMarshallFormatNone:
 | 
			
		||||
            *error = (MPMarshallError){ .type = MPMarshallSuccess };
 | 
			
		||||
            return false;
 | 
			
		||||
        case MPMarshallFormatFlat:
 | 
			
		||||
            return mpw_marshall_write_flat( out, user, error );
 | 
			
		||||
        case MPMarshallFormatJSON:
 | 
			
		||||
@@ -323,10 +340,63 @@ bool mpw_marshall_write(
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void mpw_marshall_read_flat_info(
 | 
			
		||||
        const char *in, MPMarshallInfo *info) {
 | 
			
		||||
 | 
			
		||||
    info->algorithm = MPAlgorithmVersionCurrent;
 | 
			
		||||
 | 
			
		||||
    // Parse import data.
 | 
			
		||||
    bool headerStarted = false;
 | 
			
		||||
    for (const char *endOfLine, *positionInLine = in; (endOfLine = strstr( positionInLine, "\n" )); positionInLine = endOfLine + 1) {
 | 
			
		||||
 | 
			
		||||
        // Comment or header
 | 
			
		||||
        if (*positionInLine == '#') {
 | 
			
		||||
            ++positionInLine;
 | 
			
		||||
 | 
			
		||||
            if (!headerStarted) {
 | 
			
		||||
                if (*positionInLine == '#')
 | 
			
		||||
                    // ## starts header
 | 
			
		||||
                    headerStarted = true;
 | 
			
		||||
                // Comment before header
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            if (*positionInLine == '#')
 | 
			
		||||
                // ## ends header
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            // Header
 | 
			
		||||
            char *headerName = mpw_get_token( &positionInLine, endOfLine, ":\n" );
 | 
			
		||||
            char *headerValue = mpw_get_token( &positionInLine, endOfLine, "\n" );
 | 
			
		||||
            if (!headerName || !headerValue)
 | 
			
		||||
                continue;
 | 
			
		||||
 | 
			
		||||
            if (strcmp( headerName, "Algorithm" ) == 0)
 | 
			
		||||
                info->algorithm = (MPAlgorithmVersion)atoi( headerValue );
 | 
			
		||||
            if (strcmp( headerName, "Full Name" ) == 0 || strcmp( headerName, "User Name" ) == 0)
 | 
			
		||||
                info->fullName = strdup( headerValue );
 | 
			
		||||
            if (strcmp( headerName, "Key ID" ) == 0)
 | 
			
		||||
                info->keyID = strdup( headerValue );
 | 
			
		||||
            if (strcmp( headerName, "Passwords" ) == 0)
 | 
			
		||||
                info->redacted = strcmp( headerValue, "VISIBLE" ) != 0;
 | 
			
		||||
            if (strcmp( headerName, "Date" ) == 0)
 | 
			
		||||
                info->date = mpw_mktime( headerValue );
 | 
			
		||||
 | 
			
		||||
            mpw_free_string( headerName );
 | 
			
		||||
            mpw_free_string( headerValue );
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static MPMarshalledUser *mpw_marshall_read_flat(
 | 
			
		||||
        char *in, const char *masterPassword, MPMarshallError *error) {
 | 
			
		||||
        const char *in, const char *masterPassword, MPMarshallError *error) {
 | 
			
		||||
 | 
			
		||||
    *error = (MPMarshallError){ MPMarshallErrorInternal, "Unexpected internal error." };
 | 
			
		||||
    if (!in || !strlen( in )) {
 | 
			
		||||
        error->type = MPMarshallErrorStructure;
 | 
			
		||||
        error->description = mpw_str( "No input data." );
 | 
			
		||||
        return NULL;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Parse import data.
 | 
			
		||||
    MPMasterKey masterKey = NULL;
 | 
			
		||||
@@ -336,7 +406,7 @@ static MPMarshalledUser *mpw_marshall_read_flat(
 | 
			
		||||
    MPAlgorithmVersion algorithm = MPAlgorithmVersionCurrent, masterKeyAlgorithm = (MPAlgorithmVersion)-1;
 | 
			
		||||
    MPResultType defaultType = MPResultTypeDefault;
 | 
			
		||||
    bool headerStarted = false, headerEnded = false, importRedacted = false;
 | 
			
		||||
    for (char *endOfLine, *positionInLine = in; (endOfLine = strstr( positionInLine, "\n" )); positionInLine = endOfLine + 1) {
 | 
			
		||||
    for (const char *endOfLine, *positionInLine = in; (endOfLine = strstr( positionInLine, "\n" )); positionInLine = endOfLine + 1) {
 | 
			
		||||
 | 
			
		||||
        // Comment or header
 | 
			
		||||
        if (*positionInLine == '#') {
 | 
			
		||||
@@ -541,10 +611,39 @@ static MPMarshalledUser *mpw_marshall_read_flat(
 | 
			
		||||
    return user;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void mpw_marshall_read_json_info(
 | 
			
		||||
        const char *in, MPMarshallInfo *info) {
 | 
			
		||||
 | 
			
		||||
    // Parse JSON.
 | 
			
		||||
    enum json_tokener_error json_error = json_tokener_success;
 | 
			
		||||
    json_object *json_file = json_tokener_parse_verbose( in, &json_error );
 | 
			
		||||
    if (!json_file || json_error != json_tokener_success)
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    // Section: "export"
 | 
			
		||||
    int64_t fileFormat = mpw_get_json_int( json_file, "export.format", 0 );
 | 
			
		||||
    if (fileFormat < 1)
 | 
			
		||||
        return;
 | 
			
		||||
    info->redacted = mpw_get_json_boolean( json_file, "export.redacted", true );
 | 
			
		||||
    info->date = mpw_mktime( mpw_get_json_string( json_file, "export.date", NULL ) );
 | 
			
		||||
 | 
			
		||||
    // Section: "user"
 | 
			
		||||
    info->algorithm = (MPAlgorithmVersion)mpw_get_json_int( json_file, "user.algorithm", MPAlgorithmVersionCurrent );
 | 
			
		||||
    info->fullName = strdup( mpw_get_json_string( json_file, "user.full_name", NULL ) );
 | 
			
		||||
    info->keyID = strdup( mpw_get_json_string( json_file, "user.key_id", NULL ) );
 | 
			
		||||
 | 
			
		||||
    json_object_put( json_file );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static MPMarshalledUser *mpw_marshall_read_json(
 | 
			
		||||
        char *in, const char *masterPassword, MPMarshallError *error) {
 | 
			
		||||
        const char *in, const char *masterPassword, MPMarshallError *error) {
 | 
			
		||||
 | 
			
		||||
    *error = (MPMarshallError){ MPMarshallErrorInternal, "Unexpected internal error." };
 | 
			
		||||
    if (!in || !strlen( in )) {
 | 
			
		||||
        error->type = MPMarshallErrorStructure;
 | 
			
		||||
        error->description = mpw_str( "No input data." );
 | 
			
		||||
        return NULL;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Parse JSON.
 | 
			
		||||
    enum json_tokener_error json_error = json_tokener_success;
 | 
			
		||||
@@ -683,10 +782,33 @@ static MPMarshalledUser *mpw_marshall_read_json(
 | 
			
		||||
    return user;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MPMarshallInfo *mpw_marshall_read_info(
 | 
			
		||||
        const char *in) {
 | 
			
		||||
 | 
			
		||||
    MPMarshallInfo *info = malloc( sizeof( MPMarshallInfo ) );
 | 
			
		||||
    *info = (MPMarshallInfo){ .format = MPMarshallFormatNone };
 | 
			
		||||
 | 
			
		||||
    if (in && strlen( in )) {
 | 
			
		||||
        if (in[0] == '#') {
 | 
			
		||||
            *info = (MPMarshallInfo){ .format = MPMarshallFormatFlat };
 | 
			
		||||
            mpw_marshall_read_flat_info( in, info );
 | 
			
		||||
        }
 | 
			
		||||
        else if (in[0] == '{') {
 | 
			
		||||
            *info = (MPMarshallInfo){ .format = MPMarshallFormatJSON };
 | 
			
		||||
            mpw_marshall_read_json_info( in, info );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return info;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MPMarshalledUser *mpw_marshall_read(
 | 
			
		||||
        char *in, const MPMarshallFormat inFormat, const char *masterPassword, MPMarshallError *error) {
 | 
			
		||||
        const char *in, const MPMarshallFormat inFormat, const char *masterPassword, MPMarshallError *error) {
 | 
			
		||||
 | 
			
		||||
    switch (inFormat) {
 | 
			
		||||
        case MPMarshallFormatNone:
 | 
			
		||||
            *error = (MPMarshallError){ .type = MPMarshallSuccess };
 | 
			
		||||
            return false;
 | 
			
		||||
        case MPMarshallFormatFlat:
 | 
			
		||||
            return mpw_marshall_read_flat( in, masterPassword, error );
 | 
			
		||||
        case MPMarshallFormatJSON:
 | 
			
		||||
@@ -700,6 +822,9 @@ MPMarshalledUser *mpw_marshall_read(
 | 
			
		||||
const MPMarshallFormat mpw_formatWithName(
 | 
			
		||||
        const char *formatName) {
 | 
			
		||||
 | 
			
		||||
    if (!formatName || !strlen( formatName ))
 | 
			
		||||
        return MPMarshallFormatNone;
 | 
			
		||||
 | 
			
		||||
    // Lower-case to standardize it.
 | 
			
		||||
    size_t stdFormatNameSize = strlen( formatName );
 | 
			
		||||
    char stdFormatName[stdFormatNameSize + 1];
 | 
			
		||||
@@ -707,6 +832,8 @@ const MPMarshallFormat mpw_formatWithName(
 | 
			
		||||
        stdFormatName[c] = (char)tolower( formatName[c] );
 | 
			
		||||
    stdFormatName[stdFormatNameSize] = '\0';
 | 
			
		||||
 | 
			
		||||
    if (strncmp( mpw_nameForFormat( MPMarshallFormatNone ), stdFormatName, strlen( stdFormatName ) ) == 0)
 | 
			
		||||
        return MPMarshallFormatNone;
 | 
			
		||||
    if (strncmp( mpw_nameForFormat( MPMarshallFormatFlat ), stdFormatName, strlen( stdFormatName ) ) == 0)
 | 
			
		||||
        return MPMarshallFormatFlat;
 | 
			
		||||
    if (strncmp( mpw_nameForFormat( MPMarshallFormatJSON ), stdFormatName, strlen( stdFormatName ) ) == 0)
 | 
			
		||||
@@ -720,6 +847,8 @@ const char *mpw_nameForFormat(
 | 
			
		||||
        const MPMarshallFormat format) {
 | 
			
		||||
 | 
			
		||||
    switch (format) {
 | 
			
		||||
        case MPMarshallFormatNone:
 | 
			
		||||
            return "none";
 | 
			
		||||
        case MPMarshallFormatFlat:
 | 
			
		||||
            return "flat";
 | 
			
		||||
        case MPMarshallFormatJSON:
 | 
			
		||||
@@ -735,6 +864,8 @@ const char *mpw_marshall_format_extension(
 | 
			
		||||
        const MPMarshallFormat format) {
 | 
			
		||||
 | 
			
		||||
    switch (format) {
 | 
			
		||||
        case MPMarshallFormatNone:
 | 
			
		||||
            return NULL;
 | 
			
		||||
        case MPMarshallFormatFlat:
 | 
			
		||||
            return "mpsites";
 | 
			
		||||
        case MPMarshallFormatJSON:
 | 
			
		||||
 
 | 
			
		||||
@@ -26,6 +26,8 @@
 | 
			
		||||
//// Types.
 | 
			
		||||
 | 
			
		||||
typedef enum( unsigned int, MPMarshallFormat ) {
 | 
			
		||||
    /** Generate a key for authentication. */
 | 
			
		||||
            MPMarshallFormatNone,
 | 
			
		||||
    /** Generate a key for authentication. */
 | 
			
		||||
            MPMarshallFormatFlat,
 | 
			
		||||
    /** Generate a name for identification. */
 | 
			
		||||
@@ -91,22 +93,42 @@ typedef struct MPMarshalledUser {
 | 
			
		||||
    MPMarshalledSite *sites;
 | 
			
		||||
} MPMarshalledUser;
 | 
			
		||||
 | 
			
		||||
typedef struct MPMarshallInfo {
 | 
			
		||||
    MPMarshallFormat format;
 | 
			
		||||
    MPAlgorithmVersion algorithm;
 | 
			
		||||
    const char *fullName;
 | 
			
		||||
    const char *keyID;
 | 
			
		||||
    bool redacted;
 | 
			
		||||
    time_t date;
 | 
			
		||||
} MPMarshallInfo;
 | 
			
		||||
 | 
			
		||||
//// Marshalling.
 | 
			
		||||
 | 
			
		||||
/** Write the user and all associated data out to the given output buffer using the given marshalling format. */
 | 
			
		||||
bool mpw_marshall_write(
 | 
			
		||||
        char **out, const MPMarshallFormat outFormat, const MPMarshalledUser *user, MPMarshallError *error);
 | 
			
		||||
/** Try to read metadata on the sites in the input buffer. */
 | 
			
		||||
MPMarshallInfo *mpw_marshall_read_info(
 | 
			
		||||
        const char *in);
 | 
			
		||||
/** Unmarshall sites in the given input buffer by parsing it using the given marshalling format. */
 | 
			
		||||
MPMarshalledUser *mpw_marshall_read(
 | 
			
		||||
        char *in, const MPMarshallFormat inFormat, const char *masterPassword, MPMarshallError *error);
 | 
			
		||||
        const char *in, const MPMarshallFormat inFormat, const char *masterPassword, MPMarshallError *error);
 | 
			
		||||
 | 
			
		||||
//// Utilities.
 | 
			
		||||
 | 
			
		||||
/** Create a new user object ready for marshalling. */
 | 
			
		||||
MPMarshalledUser *mpw_marshall_user(
 | 
			
		||||
        const char *fullName, const char *masterPassword, const MPAlgorithmVersion algorithmVersion);
 | 
			
		||||
/** Create a new site attached to the given user object, ready for marshalling. */
 | 
			
		||||
MPMarshalledSite *mpw_marshall_site(
 | 
			
		||||
        MPMarshalledUser *user,
 | 
			
		||||
        const char *siteName, const MPResultType resultType, const MPCounterValue siteCounter, const MPAlgorithmVersion algorithmVersion);
 | 
			
		||||
/** Create a new question attached to the given site object, ready for marshalling. */
 | 
			
		||||
MPMarshalledQuestion *mpw_marshal_question(
 | 
			
		||||
        MPMarshalledSite *site, const char *keyword);
 | 
			
		||||
/** Free the given user object and all associated data. */
 | 
			
		||||
bool mpw_marshal_info_free(
 | 
			
		||||
        MPMarshallInfo *info);
 | 
			
		||||
bool mpw_marshal_free(
 | 
			
		||||
        MPMarshalledUser *user);
 | 
			
		||||
 | 
			
		||||
@@ -122,6 +144,9 @@ const MPMarshallFormat mpw_formatWithName(
 | 
			
		||||
 */
 | 
			
		||||
const char *mpw_nameForFormat(
 | 
			
		||||
        const MPMarshallFormat format);
 | 
			
		||||
/**
 | 
			
		||||
 * @return The file extension that's recommended for files that use the given marshalling format.
 | 
			
		||||
 */
 | 
			
		||||
const char *mpw_marshall_format_extension(
 | 
			
		||||
        const MPMarshallFormat format);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -48,9 +48,9 @@ const MPResultType mpw_typeWithName(const char *typeName) {
 | 
			
		||||
        if ('n' == typeName[0])
 | 
			
		||||
            return MPResultTypeTemplateName;
 | 
			
		||||
        if ('P' == typeName[0])
 | 
			
		||||
            return MPResultTypeStatePersonal;
 | 
			
		||||
            return MPResultTypeStatefulPersonal;
 | 
			
		||||
        if ('D' == typeName[0])
 | 
			
		||||
            return MPResultTypeStateDevice;
 | 
			
		||||
            return MPResultTypeStatefulDevice;
 | 
			
		||||
        if ('k' == typeName[0])
 | 
			
		||||
            return MPResultTypeDeriveKey;
 | 
			
		||||
    }
 | 
			
		||||
@@ -82,10 +82,10 @@ const MPResultType mpw_typeWithName(const char *typeName) {
 | 
			
		||||
        return MPResultTypeTemplateName;
 | 
			
		||||
    if (strncmp( mpw_nameForType( MPResultTypeTemplatePhrase ), stdTypeName, strlen( stdTypeName ) ) == 0)
 | 
			
		||||
        return MPResultTypeTemplatePhrase;
 | 
			
		||||
    if (strncmp( mpw_nameForType( MPResultTypeStatePersonal ), stdTypeName, strlen( stdTypeName ) ) == 0)
 | 
			
		||||
        return MPResultTypeStatePersonal;
 | 
			
		||||
    if (strncmp( mpw_nameForType( MPResultTypeStateDevice ), stdTypeName, strlen( stdTypeName ) ) == 0)
 | 
			
		||||
        return MPResultTypeStateDevice;
 | 
			
		||||
    if (strncmp( mpw_nameForType( MPResultTypeStatefulPersonal ), stdTypeName, strlen( stdTypeName ) ) == 0)
 | 
			
		||||
        return MPResultTypeStatefulPersonal;
 | 
			
		||||
    if (strncmp( mpw_nameForType( MPResultTypeStatefulDevice ), stdTypeName, strlen( stdTypeName ) ) == 0)
 | 
			
		||||
        return MPResultTypeStatefulDevice;
 | 
			
		||||
    if (strncmp( mpw_nameForType( MPResultTypeDeriveKey ), stdTypeName, strlen( stdTypeName ) ) == 0)
 | 
			
		||||
        return MPResultTypeDeriveKey;
 | 
			
		||||
 | 
			
		||||
@@ -112,9 +112,9 @@ const char *mpw_nameForType(MPResultType resultType) {
 | 
			
		||||
            return "name";
 | 
			
		||||
        case MPResultTypeTemplatePhrase:
 | 
			
		||||
            return "phrase";
 | 
			
		||||
        case MPResultTypeStatePersonal:
 | 
			
		||||
        case MPResultTypeStatefulPersonal:
 | 
			
		||||
            return "personal";
 | 
			
		||||
        case MPResultTypeStateDevice:
 | 
			
		||||
        case MPResultTypeStatefulDevice:
 | 
			
		||||
            return "device";
 | 
			
		||||
        case MPResultTypeDeriveKey:
 | 
			
		||||
            return "key";
 | 
			
		||||
 
 | 
			
		||||
@@ -51,7 +51,7 @@ typedef enum( uint16_t, MPResultTypeClass ) {
 | 
			
		||||
    /** Use the site key to generate a password from a template. */
 | 
			
		||||
            MPResultTypeClassTemplate = 1 << 4,
 | 
			
		||||
    /** Use the site key to encrypt and decrypt a stateful entity. */
 | 
			
		||||
            MPResultTypeClassState = 1 << 5,
 | 
			
		||||
            MPResultTypeClassStateful = 1 << 5,
 | 
			
		||||
    /** Use the site key to derive a site-specific object. */
 | 
			
		||||
            MPResultTypeClassDerive = 1 << 6,
 | 
			
		||||
};
 | 
			
		||||
@@ -86,9 +86,9 @@ typedef enum( uint32_t, MPResultType ) {
 | 
			
		||||
            MPResultTypeTemplatePhrase = 0xF | MPResultTypeClassTemplate | 0x0,
 | 
			
		||||
 | 
			
		||||
    /** Custom saved password. */
 | 
			
		||||
            MPResultTypeStatePersonal = 0x0 | MPResultTypeClassState | MPSiteFeatureExportContent,
 | 
			
		||||
            MPResultTypeStatefulPersonal = 0x0 | MPResultTypeClassStateful | MPSiteFeatureExportContent,
 | 
			
		||||
    /** Custom saved password that should not be exported from the device. */
 | 
			
		||||
            MPResultTypeStateDevice = 0x1 | MPResultTypeClassState | MPSiteFeatureDevicePrivate,
 | 
			
		||||
            MPResultTypeStatefulDevice = 0x1 | MPResultTypeClassStateful | MPSiteFeatureDevicePrivate,
 | 
			
		||||
 | 
			
		||||
    /** Derive a unique binary key. */
 | 
			
		||||
            MPResultTypeDeriveKey = 0x0 | MPResultTypeClassDerive | MPSiteFeatureAlternative,
 | 
			
		||||
 
 | 
			
		||||
@@ -35,7 +35,9 @@
 | 
			
		||||
 | 
			
		||||
#include "mpw-util.h"
 | 
			
		||||
 | 
			
		||||
#ifdef inf_level
 | 
			
		||||
int mpw_verbosity = inf_level;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
bool mpw_push_buf(uint8_t **const buffer, size_t *const bufferSize, const void *pushBuffer, const size_t pushSize) {
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -253,6 +253,7 @@
 | 
			
		||||
		DAADBFE01A68763B00F7A756 /* mpw-algorithm.c in Sources */ = {isa = PBXBuildFile; fileRef = 93D3969393A3A46BD27D7078 /* mpw-algorithm.c */; };
 | 
			
		||||
		DAB7AE5D1F3D752900C856B1 /* libjson-c.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAB7AE5C1F3D752900C856B1 /* libjson-c.a */; };
 | 
			
		||||
		DAB7AE771F3D755B00C856B1 /* libjson-c.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAB7AE761F3D755B00C856B1 /* libjson-c.a */; };
 | 
			
		||||
		DAB7AE991F3DDEE000C856B1 /* mpw-marshall-util.c in Sources */ = {isa = PBXBuildFile; fileRef = DAB7AE981F3DDEE000C856B1 /* mpw-marshall-util.c */; };
 | 
			
		||||
		DABB981615100B4000B05417 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DABB981515100B4000B05417 /* SystemConfiguration.framework */; };
 | 
			
		||||
		DABD39371711E29700CF925C /* avatar-0.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD366C1711E29400CF925C /* avatar-0.png */; };
 | 
			
		||||
		DABD39381711E29700CF925C /* avatar-0@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD366D1711E29400CF925C /* avatar-0@2x.png */; };
 | 
			
		||||
@@ -903,6 +904,8 @@
 | 
			
		||||
		DAB7AE731F3D755B00C856B1 /* strdup_compat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = strdup_compat.h; sourceTree = "<group>"; };
 | 
			
		||||
		DAB7AE741F3D755B00C856B1 /* vasprintf_compat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = vasprintf_compat.h; sourceTree = "<group>"; };
 | 
			
		||||
		DAB7AE761F3D755B00C856B1 /* libjson-c.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libjson-c.a"; sourceTree = "<group>"; };
 | 
			
		||||
		DAB7AE971F3DDEE000C856B1 /* mpw-marshall-util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "mpw-marshall-util.h"; sourceTree = "<group>"; };
 | 
			
		||||
		DAB7AE981F3DDEE000C856B1 /* mpw-marshall-util.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-marshall-util.c"; sourceTree = "<group>"; };
 | 
			
		||||
		DABB981515100B4000B05417 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; };
 | 
			
		||||
		DABD360F1711E29400CF925C /* ui_background.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = ui_background.png; sourceTree = "<group>"; };
 | 
			
		||||
		DABD36101711E29400CF925C /* ui_background@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ui_background@2x.png"; sourceTree = "<group>"; };
 | 
			
		||||
@@ -1773,6 +1776,8 @@
 | 
			
		||||
				93D39D4E713564B7654341B0 /* mpw-algorithm_v3.c */,
 | 
			
		||||
				93D3969393A3A46BD27D7078 /* mpw-algorithm.c */,
 | 
			
		||||
				93D3990D850D76A94C6B7A4D /* mpw-algorithm.h */,
 | 
			
		||||
				DAB7AE981F3DDEE000C856B1 /* mpw-marshall-util.c */,
 | 
			
		||||
				DAB7AE971F3DDEE000C856B1 /* mpw-marshall-util.h */,
 | 
			
		||||
				DAA449D01EEC4B5800E7BDD5 /* mpw-marshall.c */,
 | 
			
		||||
				DAA449D11EEC4B5800E7BDD5 /* mpw-marshall.h */,
 | 
			
		||||
				93D392C5A6572DB0EB5B82C8 /* mpw-types.c */,
 | 
			
		||||
@@ -4030,6 +4035,7 @@
 | 
			
		||||
				DA0CC58E1EB6B030009A8ED9 /* MPSiteQuestionEntity+CoreDataClass.m in Sources */,
 | 
			
		||||
				93D39A5FF670957C0AF8298D /* MPSiteCell.m in Sources */,
 | 
			
		||||
				93D398ECD7D1A0DEDDADF516 /* MPEmergencyViewController.m in Sources */,
 | 
			
		||||
				DAB7AE991F3DDEE000C856B1 /* mpw-marshall-util.c in Sources */,
 | 
			
		||||
				DA95B50F1C4776F00067F5EF /* NSMutableSet+Pearl.m in Sources */,
 | 
			
		||||
				93D394B5036C882B33C71872 /* MPSitesSegue.m in Sources */,
 | 
			
		||||
				DA0CC5911EB6B030009A8ED9 /* MPStoredSiteEntity+CoreDataProperties.m in Sources */,
 | 
			
		||||
@@ -4327,6 +4333,8 @@
 | 
			
		||||
				GCC_WARN_UNUSED_VARIABLE = YES;
 | 
			
		||||
				HEADER_SEARCH_PATHS = (
 | 
			
		||||
					"\"$(BUILD_ROOT)/../IntermediateBuildFilesPath/UninstalledProducts/$(PLATFORM_NAME)/include\"",
 | 
			
		||||
					"\"$(PROJECT_DIR)/External/libsodium/libsodium-osx/include\"",
 | 
			
		||||
					"\"$(PROJECT_DIR)/External/libjson-c/libjson-c-osx/include\"",
 | 
			
		||||
					"$(inherited)",
 | 
			
		||||
				);
 | 
			
		||||
				IPHONEOS_DEPLOYMENT_TARGET = 8.0;
 | 
			
		||||
@@ -4510,6 +4518,8 @@
 | 
			
		||||
				GCC_WARN_UNUSED_VARIABLE = YES;
 | 
			
		||||
				HEADER_SEARCH_PATHS = (
 | 
			
		||||
					"\"$(BUILD_ROOT)/../IntermediateBuildFilesPath/UninstalledProducts/$(PLATFORM_NAME)/include\"",
 | 
			
		||||
					"\"$(PROJECT_DIR)/External/libsodium/libsodium-osx/include\"",
 | 
			
		||||
					"\"$(PROJECT_DIR)/External/libjson-c/libjson-c-osx/include\"",
 | 
			
		||||
					"$(inherited)",
 | 
			
		||||
				);
 | 
			
		||||
				IPHONEOS_DEPLOYMENT_TARGET = 8.0;
 | 
			
		||||
@@ -4598,6 +4608,8 @@
 | 
			
		||||
				GCC_WARN_UNUSED_VARIABLE = YES;
 | 
			
		||||
				HEADER_SEARCH_PATHS = (
 | 
			
		||||
					"\"$(BUILD_ROOT)/../IntermediateBuildFilesPath/UninstalledProducts/$(PLATFORM_NAME)/include\"",
 | 
			
		||||
					"\"$(PROJECT_DIR)/External/libsodium/libsodium-osx/include\"",
 | 
			
		||||
					"\"$(PROJECT_DIR)/External/libjson-c/libjson-c-osx/include\"",
 | 
			
		||||
					"$(inherited)",
 | 
			
		||||
				);
 | 
			
		||||
				IPHONEOS_DEPLOYMENT_TARGET = 8.0;
 | 
			
		||||
 
 | 
			
		||||
@@ -2979,8 +2979,9 @@
 | 
			
		||||
				GCC_WARN_UNUSED_VARIABLE = YES;
 | 
			
		||||
				HEADER_SEARCH_PATHS = (
 | 
			
		||||
					"\"$(BUILD_ROOT)/../IntermediateBuildFilesPath/UninstalledProducts/$(PLATFORM_NAME)/include\"",
 | 
			
		||||
					"\"$(PROJECT_DIR)/External/libsodium/libsodium-osx/include\"",
 | 
			
		||||
					"\"$(PROJECT_DIR)/External/libjson-c/libjson-c-osx/include\"",
 | 
			
		||||
					"$(inherited)",
 | 
			
		||||
					"$(PROJECT_DIR)/External/libjson-c/libjson-c-osx/include",
 | 
			
		||||
				);
 | 
			
		||||
				LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)";
 | 
			
		||||
				LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks";
 | 
			
		||||
@@ -3315,8 +3316,9 @@
 | 
			
		||||
				GCC_WARN_UNUSED_VARIABLE = YES;
 | 
			
		||||
				HEADER_SEARCH_PATHS = (
 | 
			
		||||
					"\"$(BUILD_ROOT)/../IntermediateBuildFilesPath/UninstalledProducts/$(PLATFORM_NAME)/include\"",
 | 
			
		||||
					"\"$(PROJECT_DIR)/External/libsodium/libsodium-osx/include\"",
 | 
			
		||||
					"\"$(PROJECT_DIR)/External/libjson-c/libjson-c-osx/include\"",
 | 
			
		||||
					"$(inherited)",
 | 
			
		||||
					"$(PROJECT_DIR)/External/libjson-c/libjson-c-osx/include",
 | 
			
		||||
				);
 | 
			
		||||
				LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)";
 | 
			
		||||
				LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks";
 | 
			
		||||
@@ -3407,8 +3409,9 @@
 | 
			
		||||
				GCC_WARN_UNUSED_VARIABLE = YES;
 | 
			
		||||
				HEADER_SEARCH_PATHS = (
 | 
			
		||||
					"\"$(BUILD_ROOT)/../IntermediateBuildFilesPath/UninstalledProducts/$(PLATFORM_NAME)/include\"",
 | 
			
		||||
					"\"$(PROJECT_DIR)/External/libsodium/libsodium-osx/include\"",
 | 
			
		||||
					"\"$(PROJECT_DIR)/External/libjson-c/libjson-c-osx/include\"",
 | 
			
		||||
					"$(inherited)",
 | 
			
		||||
					"$(PROJECT_DIR)/External/libjson-c/libjson-c-osx/include",
 | 
			
		||||
				);
 | 
			
		||||
				LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)";
 | 
			
		||||
				LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks";
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,5 @@
 | 
			
		||||
#!/usr/bin/env bash
 | 
			
		||||
set -e
 | 
			
		||||
echo "ARGS: $*"
 | 
			
		||||
 | 
			
		||||
cd "${BASH_SOURCE%/*}/../External/libjson-c"
 | 
			
		||||
[[ -d libjson-c-ios ]] && exit
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,5 @@
 | 
			
		||||
#!/usr/bin/env bash
 | 
			
		||||
set -e
 | 
			
		||||
echo "ARGS: $*"
 | 
			
		||||
 | 
			
		||||
cd "${BASH_SOURCE%/*}/../External/libjson-c"
 | 
			
		||||
[[ -d libjson-c-osx ]] && exit
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,5 @@
 | 
			
		||||
#!/usr/bin/env bash
 | 
			
		||||
set -e
 | 
			
		||||
echo "ARGS: $*"
 | 
			
		||||
 | 
			
		||||
cd "${BASH_SOURCE%/*}/../External/libsodium"
 | 
			
		||||
[[ -d libsodium-ios ]] && exit
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,5 @@
 | 
			
		||||
#!/usr/bin/env bash
 | 
			
		||||
set -e
 | 
			
		||||
echo "ARGS: $*"
 | 
			
		||||
 | 
			
		||||
cd "${BASH_SOURCE%/*}/../External/libsodium"
 | 
			
		||||
[[ -d libsodium-osx ]] && exit
 | 
			
		||||
 
 | 
			
		||||
@@ -52,49 +52,44 @@ NSString *NSStringFromTimeToCrack(TimeToCrack timeToCrack);
 | 
			
		||||
- (NSData *)keyIDForKey:(MPMasterKey)masterKey;
 | 
			
		||||
- (NSData *)keyDataForFullName:(NSString *)fullName withMasterPassword:(NSString *)masterPassword;
 | 
			
		||||
 | 
			
		||||
- (NSString *)nameOfType:(MPSiteType)type;
 | 
			
		||||
- (NSString *)shortNameOfType:(MPSiteType)type;
 | 
			
		||||
- (NSString *)classNameOfType:(MPSiteType)type;
 | 
			
		||||
- (Class)classOfType:(MPSiteType)type;
 | 
			
		||||
- (NSString *)nameOfType:(MPResultType)type;
 | 
			
		||||
- (NSString *)shortNameOfType:(MPResultType)type;
 | 
			
		||||
- (NSString *)classNameOfType:(MPResultType)type;
 | 
			
		||||
- (Class)classOfType:(MPResultType)type;
 | 
			
		||||
- (NSArray *)allTypes;
 | 
			
		||||
- (NSArray *)allTypesStartingWith:(MPSiteType)startingType;
 | 
			
		||||
- (MPSiteType)defaultType;
 | 
			
		||||
- (MPSiteType)nextType:(MPSiteType)type;
 | 
			
		||||
- (MPSiteType)previousType:(MPSiteType)type;
 | 
			
		||||
- (NSArray *)allTypesStartingWith:(MPResultType)startingType;
 | 
			
		||||
- (MPResultType)defaultType;
 | 
			
		||||
- (MPResultType)nextType:(MPResultType)type;
 | 
			
		||||
- (MPResultType)previousType:(MPResultType)type;
 | 
			
		||||
 | 
			
		||||
- (NSString *)generateLoginForSiteNamed:(NSString *)name usingKey:(MPKey *)key;
 | 
			
		||||
- (NSString *)generatePasswordForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter
 | 
			
		||||
                                  usingKey:(MPKey *)key;
 | 
			
		||||
- (NSString *)generateAnswerForSiteNamed:(NSString *)name onQuestion:(NSString *)question usingKey:(MPKey *)key;
 | 
			
		||||
- (NSString *)generateContentForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter
 | 
			
		||||
                                  variant:(MPSiteVariant)variant context:(NSString *)context usingKey:(MPKey *)key;
 | 
			
		||||
- (NSString *)mpwLoginForSiteNamed:(NSString *)name usingKey:(MPKey *)key;
 | 
			
		||||
- (NSString *)mpwTemplateForSiteNamed:(NSString *)name ofType:(MPResultType)type
 | 
			
		||||
                          withCounter:(NSUInteger)counter usingKey:(MPKey *)key;
 | 
			
		||||
- (NSString *)mpwAnswerForSiteNamed:(NSString *)name onQuestion:(NSString *)question usingKey:(MPKey *)key;
 | 
			
		||||
- (NSString *)mpwResultForSiteNamed:(NSString *)name ofType:(MPResultType)type parameter:(NSString *)parameter
 | 
			
		||||
                        withCounter:(NSUInteger)counter variant:(MPKeyPurpose)purpose context:(NSString *)context usingKey:(MPKey *)key;
 | 
			
		||||
 | 
			
		||||
- (NSString *)storedLoginForSite:(MPStoredSiteEntity *)site usingKey:(MPKey *)key;
 | 
			
		||||
- (NSString *)storedPasswordForSite:(MPStoredSiteEntity *)site usingKey:(MPKey *)key;
 | 
			
		||||
- (BOOL)savePassword:(NSString *)clearPassword toSite:(MPSiteEntity *)site usingKey:(MPKey *)key;
 | 
			
		||||
 | 
			
		||||
- (BOOL)savePassword:(NSString *)clearPassword toSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey;
 | 
			
		||||
- (NSString *)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)key;
 | 
			
		||||
- (NSString *)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)key;
 | 
			
		||||
- (NSString *)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)key;
 | 
			
		||||
- (NSString *)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)key;
 | 
			
		||||
 | 
			
		||||
- (NSString *)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey;
 | 
			
		||||
- (NSString *)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey;
 | 
			
		||||
- (NSString *)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey;
 | 
			
		||||
- (NSString *)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)siteKey;
 | 
			
		||||
 | 
			
		||||
- (void)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey
 | 
			
		||||
- (void)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)key
 | 
			
		||||
                     result:(void ( ^ )(NSString *result))resultBlock;
 | 
			
		||||
- (void)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey
 | 
			
		||||
- (void)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)key
 | 
			
		||||
                        result:(void ( ^ )(NSString *result))resultBlock;
 | 
			
		||||
- (void)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey
 | 
			
		||||
- (void)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)key
 | 
			
		||||
                      result:(void ( ^ )(NSString *result))resultBlock;
 | 
			
		||||
- (void)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)siteKey
 | 
			
		||||
- (void)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)key
 | 
			
		||||
                          result:(void ( ^ )(NSString *result))resultBlock;
 | 
			
		||||
 | 
			
		||||
- (void)importProtectedPassword:(NSString *)protectedPassword protectedByKey:(MPKey *)importKey
 | 
			
		||||
                       intoSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey;
 | 
			
		||||
- (void)importClearTextPassword:(NSString *)clearPassword intoSite:(MPSiteEntity *)site
 | 
			
		||||
                       usingKey:(MPKey *)siteKey;
 | 
			
		||||
- (NSString *)exportPasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey;
 | 
			
		||||
- (void)importPassword:(NSString *)protectedPassword protectedByKey:(MPKey *)importKey
 | 
			
		||||
              intoSite:(MPSiteEntity *)site usingKey:(MPKey *)key;
 | 
			
		||||
- (NSString *)exportPasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)key;
 | 
			
		||||
 | 
			
		||||
- (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordOfType:(MPSiteType)type byAttacker:(MPAttacker)attacker;
 | 
			
		||||
- (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordOfType:(MPResultType)type byAttacker:(MPAttacker)attacker;
 | 
			
		||||
- (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordString:(NSString *)password byAttacker:(MPAttacker)attacker;
 | 
			
		||||
 | 
			
		||||
@end
 | 
			
		||||
 
 | 
			
		||||
@@ -145,125 +145,125 @@ static NSOperationQueue *_mpwQueue = nil;
 | 
			
		||||
    return [[NSData dataWithBytesNoCopy:(void *)masterKey length:MPMasterKeySize] hashWith:PearlHashSHA256];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (NSString *)nameOfType:(MPSiteType)type {
 | 
			
		||||
- (NSString *)nameOfType:(MPResultType)type {
 | 
			
		||||
 | 
			
		||||
    if (!type)
 | 
			
		||||
        return nil;
 | 
			
		||||
 | 
			
		||||
    switch (type) {
 | 
			
		||||
        case MPSiteTypeGeneratedMaximum:
 | 
			
		||||
        case MPResultTypeTemplateMaximum:
 | 
			
		||||
            return @"Maximum Security Password";
 | 
			
		||||
 | 
			
		||||
        case MPSiteTypeGeneratedLong:
 | 
			
		||||
        case MPResultTypeTemplateLong:
 | 
			
		||||
            return @"Long Password";
 | 
			
		||||
 | 
			
		||||
        case MPSiteTypeGeneratedMedium:
 | 
			
		||||
        case MPResultTypeTemplateMedium:
 | 
			
		||||
            return @"Medium Password";
 | 
			
		||||
 | 
			
		||||
        case MPSiteTypeGeneratedBasic:
 | 
			
		||||
        case MPResultTypeTemplateBasic:
 | 
			
		||||
            return @"Basic Password";
 | 
			
		||||
 | 
			
		||||
        case MPSiteTypeGeneratedShort:
 | 
			
		||||
        case MPResultTypeTemplateShort:
 | 
			
		||||
            return @"Short Password";
 | 
			
		||||
 | 
			
		||||
        case MPSiteTypeGeneratedPIN:
 | 
			
		||||
        case MPResultTypeTemplatePIN:
 | 
			
		||||
            return @"PIN";
 | 
			
		||||
 | 
			
		||||
        case MPSiteTypeGeneratedName:
 | 
			
		||||
        case MPResultTypeTemplateName:
 | 
			
		||||
            return @"Name";
 | 
			
		||||
 | 
			
		||||
        case MPSiteTypeGeneratedPhrase:
 | 
			
		||||
        case MPResultTypeTemplatePhrase:
 | 
			
		||||
            return @"Phrase";
 | 
			
		||||
 | 
			
		||||
        case MPSiteTypeStoredPersonal:
 | 
			
		||||
        case MPResultTypeStatefulPersonal:
 | 
			
		||||
            return @"Personal Password";
 | 
			
		||||
 | 
			
		||||
        case MPSiteTypeStoredDevicePrivate:
 | 
			
		||||
        case MPResultTypeStatefulDevice:
 | 
			
		||||
            return @"Device Private Password";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Throw( @"Type not supported: %lu", (long)type );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (NSString *)shortNameOfType:(MPSiteType)type {
 | 
			
		||||
- (NSString *)shortNameOfType:(MPResultType)type {
 | 
			
		||||
 | 
			
		||||
    if (!type)
 | 
			
		||||
        return nil;
 | 
			
		||||
 | 
			
		||||
    switch (type) {
 | 
			
		||||
        case MPSiteTypeGeneratedMaximum:
 | 
			
		||||
        case MPResultTypeTemplateMaximum:
 | 
			
		||||
            return @"Maximum";
 | 
			
		||||
 | 
			
		||||
        case MPSiteTypeGeneratedLong:
 | 
			
		||||
        case MPResultTypeTemplateLong:
 | 
			
		||||
            return @"Long";
 | 
			
		||||
 | 
			
		||||
        case MPSiteTypeGeneratedMedium:
 | 
			
		||||
        case MPResultTypeTemplateMedium:
 | 
			
		||||
            return @"Medium";
 | 
			
		||||
 | 
			
		||||
        case MPSiteTypeGeneratedBasic:
 | 
			
		||||
        case MPResultTypeTemplateBasic:
 | 
			
		||||
            return @"Basic";
 | 
			
		||||
 | 
			
		||||
        case MPSiteTypeGeneratedShort:
 | 
			
		||||
        case MPResultTypeTemplateShort:
 | 
			
		||||
            return @"Short";
 | 
			
		||||
 | 
			
		||||
        case MPSiteTypeGeneratedPIN:
 | 
			
		||||
        case MPResultTypeTemplatePIN:
 | 
			
		||||
            return @"PIN";
 | 
			
		||||
 | 
			
		||||
        case MPSiteTypeGeneratedName:
 | 
			
		||||
        case MPResultTypeTemplateName:
 | 
			
		||||
            return @"Name";
 | 
			
		||||
 | 
			
		||||
        case MPSiteTypeGeneratedPhrase:
 | 
			
		||||
        case MPResultTypeTemplatePhrase:
 | 
			
		||||
            return @"Phrase";
 | 
			
		||||
 | 
			
		||||
        case MPSiteTypeStoredPersonal:
 | 
			
		||||
        case MPResultTypeStatefulPersonal:
 | 
			
		||||
            return @"Personal";
 | 
			
		||||
 | 
			
		||||
        case MPSiteTypeStoredDevicePrivate:
 | 
			
		||||
        case MPResultTypeStatefulDevice:
 | 
			
		||||
            return @"Device";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Throw( @"Type not supported: %lu", (long)type );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (NSString *)classNameOfType:(MPSiteType)type {
 | 
			
		||||
- (NSString *)classNameOfType:(MPResultType)type {
 | 
			
		||||
 | 
			
		||||
    return NSStringFromClass( [self classOfType:type] );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (Class)classOfType:(MPSiteType)type {
 | 
			
		||||
- (Class)classOfType:(MPResultType)type {
 | 
			
		||||
 | 
			
		||||
    if (!type)
 | 
			
		||||
        Throw( @"No type given." );
 | 
			
		||||
 | 
			
		||||
    switch (type) {
 | 
			
		||||
        case MPSiteTypeGeneratedMaximum:
 | 
			
		||||
        case MPResultTypeTemplateMaximum:
 | 
			
		||||
            return [MPGeneratedSiteEntity class];
 | 
			
		||||
 | 
			
		||||
        case MPSiteTypeGeneratedLong:
 | 
			
		||||
        case MPResultTypeTemplateLong:
 | 
			
		||||
            return [MPGeneratedSiteEntity class];
 | 
			
		||||
 | 
			
		||||
        case MPSiteTypeGeneratedMedium:
 | 
			
		||||
        case MPResultTypeTemplateMedium:
 | 
			
		||||
            return [MPGeneratedSiteEntity class];
 | 
			
		||||
 | 
			
		||||
        case MPSiteTypeGeneratedBasic:
 | 
			
		||||
        case MPResultTypeTemplateBasic:
 | 
			
		||||
            return [MPGeneratedSiteEntity class];
 | 
			
		||||
 | 
			
		||||
        case MPSiteTypeGeneratedShort:
 | 
			
		||||
        case MPResultTypeTemplateShort:
 | 
			
		||||
            return [MPGeneratedSiteEntity class];
 | 
			
		||||
 | 
			
		||||
        case MPSiteTypeGeneratedPIN:
 | 
			
		||||
        case MPResultTypeTemplatePIN:
 | 
			
		||||
            return [MPGeneratedSiteEntity class];
 | 
			
		||||
 | 
			
		||||
        case MPSiteTypeGeneratedName:
 | 
			
		||||
        case MPResultTypeTemplateName:
 | 
			
		||||
            return [MPGeneratedSiteEntity class];
 | 
			
		||||
 | 
			
		||||
        case MPSiteTypeGeneratedPhrase:
 | 
			
		||||
        case MPResultTypeTemplatePhrase:
 | 
			
		||||
            return [MPGeneratedSiteEntity class];
 | 
			
		||||
 | 
			
		||||
        case MPSiteTypeStoredPersonal:
 | 
			
		||||
        case MPResultTypeStatefulPersonal:
 | 
			
		||||
            return [MPStoredSiteEntity class];
 | 
			
		||||
 | 
			
		||||
        case MPSiteTypeStoredDevicePrivate:
 | 
			
		||||
        case MPResultTypeStatefulDevice:
 | 
			
		||||
            return [MPStoredSiteEntity class];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -272,13 +272,13 @@ static NSOperationQueue *_mpwQueue = nil;
 | 
			
		||||
 | 
			
		||||
- (NSArray *)allTypes {
 | 
			
		||||
 | 
			
		||||
    return [self allTypesStartingWith:MPSiteTypeGeneratedPhrase];
 | 
			
		||||
    return [self allTypesStartingWith:MPResultTypeTemplatePhrase];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (NSArray *)allTypesStartingWith:(MPSiteType)startingType {
 | 
			
		||||
- (NSArray *)allTypesStartingWith:(MPResultType)startingType {
 | 
			
		||||
 | 
			
		||||
    NSMutableArray *allTypes = [[NSMutableArray alloc] initWithCapacity:8];
 | 
			
		||||
    MPSiteType currentType = startingType;
 | 
			
		||||
    MPResultType currentType = startingType;
 | 
			
		||||
    do {
 | 
			
		||||
        [allTypes addObject:@(currentType)];
 | 
			
		||||
    } while ((currentType = [self nextType:currentType]) != startingType);
 | 
			
		||||
@@ -286,199 +286,170 @@ static NSOperationQueue *_mpwQueue = nil;
 | 
			
		||||
    return allTypes;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (MPSiteType)defaultType {
 | 
			
		||||
- (MPResultType)defaultType {
 | 
			
		||||
 | 
			
		||||
    return MPSiteTypeGeneratedLong;
 | 
			
		||||
    return MPResultTypeTemplateLong;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (MPSiteType)nextType:(MPSiteType)type {
 | 
			
		||||
- (MPResultType)nextType:(MPResultType)type {
 | 
			
		||||
 | 
			
		||||
    switch (type) {
 | 
			
		||||
        case MPSiteTypeGeneratedPhrase:
 | 
			
		||||
            return MPSiteTypeGeneratedName;
 | 
			
		||||
        case MPSiteTypeGeneratedName:
 | 
			
		||||
            return MPSiteTypeGeneratedMaximum;
 | 
			
		||||
        case MPSiteTypeGeneratedMaximum:
 | 
			
		||||
            return MPSiteTypeGeneratedLong;
 | 
			
		||||
        case MPSiteTypeGeneratedLong:
 | 
			
		||||
            return MPSiteTypeGeneratedMedium;
 | 
			
		||||
        case MPSiteTypeGeneratedMedium:
 | 
			
		||||
            return MPSiteTypeGeneratedBasic;
 | 
			
		||||
        case MPSiteTypeGeneratedBasic:
 | 
			
		||||
            return MPSiteTypeGeneratedShort;
 | 
			
		||||
        case MPSiteTypeGeneratedShort:
 | 
			
		||||
            return MPSiteTypeGeneratedPIN;
 | 
			
		||||
        case MPSiteTypeGeneratedPIN:
 | 
			
		||||
            return MPSiteTypeStoredPersonal;
 | 
			
		||||
        case MPSiteTypeStoredPersonal:
 | 
			
		||||
            return MPSiteTypeStoredDevicePrivate;
 | 
			
		||||
        case MPSiteTypeStoredDevicePrivate:
 | 
			
		||||
            return MPSiteTypeGeneratedPhrase;
 | 
			
		||||
        case MPResultTypeTemplatePhrase:
 | 
			
		||||
            return MPResultTypeTemplateName;
 | 
			
		||||
        case MPResultTypeTemplateName:
 | 
			
		||||
            return MPResultTypeTemplateMaximum;
 | 
			
		||||
        case MPResultTypeTemplateMaximum:
 | 
			
		||||
            return MPResultTypeTemplateLong;
 | 
			
		||||
        case MPResultTypeTemplateLong:
 | 
			
		||||
            return MPResultTypeTemplateMedium;
 | 
			
		||||
        case MPResultTypeTemplateMedium:
 | 
			
		||||
            return MPResultTypeTemplateBasic;
 | 
			
		||||
        case MPResultTypeTemplateBasic:
 | 
			
		||||
            return MPResultTypeTemplateShort;
 | 
			
		||||
        case MPResultTypeTemplateShort:
 | 
			
		||||
            return MPResultTypeTemplatePIN;
 | 
			
		||||
        case MPResultTypeTemplatePIN:
 | 
			
		||||
            return MPResultTypeStatefulPersonal;
 | 
			
		||||
        case MPResultTypeStatefulPersonal:
 | 
			
		||||
            return MPResultTypeStatefulDevice;
 | 
			
		||||
        case MPResultTypeStatefulDevice:
 | 
			
		||||
            return MPResultTypeTemplatePhrase;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return [self defaultType];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (MPSiteType)previousType:(MPSiteType)type {
 | 
			
		||||
- (MPResultType)previousType:(MPResultType)type {
 | 
			
		||||
 | 
			
		||||
    MPSiteType previousType = type, nextType = type;
 | 
			
		||||
    MPResultType previousType = type, nextType = type;
 | 
			
		||||
    while ((nextType = [self nextType:nextType]) != type)
 | 
			
		||||
        previousType = nextType;
 | 
			
		||||
 | 
			
		||||
    return previousType;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (NSString *)generateLoginForSiteNamed:(NSString *)name usingKey:(MPKey *)key {
 | 
			
		||||
- (NSString *)mpwLoginForSiteNamed:(NSString *)name usingKey:(MPKey *)key {
 | 
			
		||||
 | 
			
		||||
    return [self generateContentForSiteNamed:name ofType:MPSiteTypeGeneratedName withCounter:1
 | 
			
		||||
                                     variant:MPKeyPurposeIdentification context:nil usingKey:key];
 | 
			
		||||
    return [self mpwResultForSiteNamed:name ofType:MPResultTypeTemplateName parameter:nil withCounter:1
 | 
			
		||||
                               variant:MPKeyPurposeIdentification context:nil usingKey:key];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (NSString *)generatePasswordForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter
 | 
			
		||||
                                  usingKey:(MPKey *)key {
 | 
			
		||||
- (NSString *)mpwTemplateForSiteNamed:(NSString *)name ofType:(MPResultType)type
 | 
			
		||||
                          withCounter:(NSUInteger)counter usingKey:(MPKey *)key {
 | 
			
		||||
 | 
			
		||||
    return [self generateContentForSiteNamed:name ofType:type withCounter:counter
 | 
			
		||||
                                     variant:MPKeyPurposeAuthentication context:nil usingKey:key];
 | 
			
		||||
    return [self mpwResultForSiteNamed:name ofType:type parameter:nil withCounter:counter
 | 
			
		||||
                               variant:MPKeyPurposeAuthentication context:nil usingKey:key];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (NSString *)generateAnswerForSiteNamed:(NSString *)name onQuestion:(NSString *)question usingKey:(MPKey *)key {
 | 
			
		||||
- (NSString *)mpwAnswerForSiteNamed:(NSString *)name onQuestion:(NSString *)question usingKey:(MPKey *)key {
 | 
			
		||||
 | 
			
		||||
    return [self generateContentForSiteNamed:name ofType:MPSiteTypeGeneratedPhrase withCounter:1
 | 
			
		||||
                                     variant:MPKeyPurposeRecovery context:question usingKey:key];
 | 
			
		||||
    return [self mpwResultForSiteNamed:name ofType:MPResultTypeTemplatePhrase parameter:nil withCounter:1
 | 
			
		||||
                               variant:MPKeyPurposeRecovery context:question usingKey:key];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (NSString *)generateContentForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter
 | 
			
		||||
                                  variant:(MPSiteVariant)variant context:(NSString *)context usingKey:(MPKey *)key {
 | 
			
		||||
- (NSString *)mpwResultForSiteNamed:(NSString *)name ofType:(MPResultType)type parameter:(NSString *)parameter
 | 
			
		||||
                        withCounter:(NSUInteger)counter variant:(MPKeyPurpose)purpose context:(NSString *)context usingKey:(MPKey *)key {
 | 
			
		||||
 | 
			
		||||
    __block NSString *content = nil;
 | 
			
		||||
    __block NSString *result = nil;
 | 
			
		||||
    [self mpw_perform:^{
 | 
			
		||||
        char const *contentBytes = mpw_passwordForSite( [key keyForAlgorithm:self],
 | 
			
		||||
                name.UTF8String, type, (uint32_t)counter, variant, context.UTF8String, [self version] );
 | 
			
		||||
        if (contentBytes) {
 | 
			
		||||
            content = [NSString stringWithCString:contentBytes encoding:NSUTF8StringEncoding];
 | 
			
		||||
            mpw_free_string( contentBytes );
 | 
			
		||||
        char const *resultBytes = mpw_siteResult( [key keyForAlgorithm:self],
 | 
			
		||||
                name.UTF8String, (uint32_t)counter, purpose, context.UTF8String, type, parameter.UTF8String, [self version] );
 | 
			
		||||
        if (resultBytes) {
 | 
			
		||||
            result = [NSString stringWithCString:resultBytes encoding:NSUTF8StringEncoding];
 | 
			
		||||
            mpw_free_string( resultBytes );
 | 
			
		||||
        }
 | 
			
		||||
    }];
 | 
			
		||||
 | 
			
		||||
    return content;
 | 
			
		||||
    return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (NSString *)storedLoginForSite:(MPStoredSiteEntity *)site usingKey:(MPKey *)key {
 | 
			
		||||
- (BOOL)savePassword:(NSString *)plainText toSite:(MPSiteEntity *)site usingKey:(MPKey *)key {
 | 
			
		||||
 | 
			
		||||
    return nil;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (NSString *)storedPasswordForSite:(MPStoredSiteEntity *)site usingKey:(MPKey *)key {
 | 
			
		||||
 | 
			
		||||
    return [self decryptContent:site.contentObject usingKey:key];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (BOOL)savePassword:(NSString *)clearContent toSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
 | 
			
		||||
 | 
			
		||||
    NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
 | 
			
		||||
    switch (site.type) {
 | 
			
		||||
        case MPSiteTypeGeneratedMaximum:
 | 
			
		||||
        case MPSiteTypeGeneratedLong:
 | 
			
		||||
        case MPSiteTypeGeneratedMedium:
 | 
			
		||||
        case MPSiteTypeGeneratedBasic:
 | 
			
		||||
        case MPSiteTypeGeneratedShort:
 | 
			
		||||
        case MPSiteTypeGeneratedPIN:
 | 
			
		||||
        case MPSiteTypeGeneratedName:
 | 
			
		||||
        case MPSiteTypeGeneratedPhrase: {
 | 
			
		||||
            wrn( @"Cannot save content to site with generated type %lu.", (long)site.type );
 | 
			
		||||
            return NO;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        case MPSiteTypeStoredPersonal: {
 | 
			
		||||
            if (![site isKindOfClass:[MPStoredSiteEntity class]]) {
 | 
			
		||||
                wrn( @"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.",
 | 
			
		||||
                        (long)site.type, [site class] );
 | 
			
		||||
                return NO;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            NSData *encryptionKey = [siteKey keyForAlgorithm:self trimmedLength:PearlCryptKeySize];
 | 
			
		||||
            NSData *encryptedContent = [[clearContent dataUsingEncoding:NSUTF8StringEncoding]
 | 
			
		||||
                    encryptWithSymmetricKey:encryptionKey padding:YES];
 | 
			
		||||
            if ([((MPStoredSiteEntity *)site).contentObject isEqualToData:encryptedContent])
 | 
			
		||||
                return NO;
 | 
			
		||||
 | 
			
		||||
            ((MPStoredSiteEntity *)site).contentObject = encryptedContent;
 | 
			
		||||
            return YES;
 | 
			
		||||
        }
 | 
			
		||||
        case MPSiteTypeStoredDevicePrivate: {
 | 
			
		||||
            if (![site isKindOfClass:[MPStoredSiteEntity class]]) {
 | 
			
		||||
                wrn( @"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.",
 | 
			
		||||
                        (long)site.type, [site class] );
 | 
			
		||||
                return NO;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            NSData *encryptionKey = [siteKey keyForAlgorithm:self trimmedLength:PearlCryptKeySize];
 | 
			
		||||
            NSData *encryptedContent = [[clearContent dataUsingEncoding:NSUTF8StringEncoding]
 | 
			
		||||
                    encryptWithSymmetricKey:encryptionKey padding:YES];
 | 
			
		||||
            NSDictionary *siteQuery = [self queryForDevicePrivateSiteNamed:site.name];
 | 
			
		||||
            if (!encryptedContent)
 | 
			
		||||
                [PearlKeyChain deleteItemForQuery:siteQuery];
 | 
			
		||||
            else
 | 
			
		||||
                [PearlKeyChain addOrUpdateItemForQuery:siteQuery withAttributes:@{
 | 
			
		||||
                        (__bridge id)kSecValueData     : encryptedContent,
 | 
			
		||||
#if TARGET_OS_IPHONE
 | 
			
		||||
                        (__bridge id)kSecAttrAccessible: (__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
 | 
			
		||||
#endif
 | 
			
		||||
                }];
 | 
			
		||||
            ((MPStoredSiteEntity *)site).contentObject = nil;
 | 
			
		||||
            return YES;
 | 
			
		||||
        }
 | 
			
		||||
    if (!(site.type & MPResultTypeClassStateful)) {
 | 
			
		||||
        wrn( @"Can only save content to site with a stateful type: %lu.", (long)site.type );
 | 
			
		||||
        return NO;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Throw( @"Unsupported type: %ld", (long)site.type );
 | 
			
		||||
    NSAssert( [[key keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
 | 
			
		||||
    if (![site isKindOfClass:[MPStoredSiteEntity class]]) {
 | 
			
		||||
        wrn( @"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.",
 | 
			
		||||
                (long)site.type, [site class] );
 | 
			
		||||
        return NO;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    __block NSData *state = nil;
 | 
			
		||||
    if (plainText)
 | 
			
		||||
        [self mpw_perform:^{
 | 
			
		||||
            char const *stateBytes = mpw_siteState( [key keyForAlgorithm:self], site.name.UTF8String,
 | 
			
		||||
                    MPCounterValueInitial, MPKeyPurposeAuthentication, NULL, site.type, plainText.UTF8String, [self version] );
 | 
			
		||||
            if (stateBytes) {
 | 
			
		||||
                state = [[NSString stringWithCString:stateBytes encoding:NSUTF8StringEncoding] decodeBase64];
 | 
			
		||||
                mpw_free_string( stateBytes );
 | 
			
		||||
            }
 | 
			
		||||
        }];
 | 
			
		||||
 | 
			
		||||
    NSDictionary *siteQuery = [self queryForSite:site];
 | 
			
		||||
    if (!state)
 | 
			
		||||
        [PearlKeyChain deleteItemForQuery:siteQuery];
 | 
			
		||||
    else
 | 
			
		||||
        [PearlKeyChain addOrUpdateItemForQuery:siteQuery withAttributes:@{
 | 
			
		||||
                (__bridge id)kSecValueData     : state,
 | 
			
		||||
#if TARGET_OS_IPHONE
 | 
			
		||||
                (__bridge id)kSecAttrAccessible:
 | 
			
		||||
                site.type & MPSiteFeatureDevicePrivate? (__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly
 | 
			
		||||
                                                      : (__bridge id)kSecAttrAccessibleWhenUnlocked,
 | 
			
		||||
#endif
 | 
			
		||||
        }];
 | 
			
		||||
    ((MPStoredSiteEntity *)site).contentObject = nil;
 | 
			
		||||
    return YES;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (NSString *)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
 | 
			
		||||
- (NSString *)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)key {
 | 
			
		||||
 | 
			
		||||
    return PearlAwait( ^(void (^setResult)(id)) {
 | 
			
		||||
        [self resolveLoginForSite:site usingKey:siteKey result:^(NSString *result_) {
 | 
			
		||||
        [self resolveLoginForSite:site usingKey:key result:^(NSString *result_) {
 | 
			
		||||
            setResult( result_ );
 | 
			
		||||
        }];
 | 
			
		||||
    } );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (NSString *)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
 | 
			
		||||
- (NSString *)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)key {
 | 
			
		||||
 | 
			
		||||
    return PearlAwait( ^(void (^setResult)(id)) {
 | 
			
		||||
        [self resolvePasswordForSite:site usingKey:siteKey result:^(NSString *result_) {
 | 
			
		||||
        [self resolvePasswordForSite:site usingKey:key result:^(NSString *result_) {
 | 
			
		||||
            setResult( result_ );
 | 
			
		||||
        }];
 | 
			
		||||
    } );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (NSString *)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
 | 
			
		||||
- (NSString *)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)key {
 | 
			
		||||
 | 
			
		||||
    return PearlAwait( ^(void (^setResult)(id)) {
 | 
			
		||||
        [self resolveAnswerForSite:site usingKey:siteKey result:^(NSString *result_) {
 | 
			
		||||
        [self resolveAnswerForSite:site usingKey:key result:^(NSString *result_) {
 | 
			
		||||
            setResult( result_ );
 | 
			
		||||
        }];
 | 
			
		||||
    } );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (NSString *)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)siteKey {
 | 
			
		||||
- (NSString *)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)key {
 | 
			
		||||
 | 
			
		||||
    return PearlAwait( ^(void (^setResult)(id)) {
 | 
			
		||||
        [self resolveAnswerForQuestion:question usingKey:siteKey result:^(NSString *result_) {
 | 
			
		||||
        [self resolveAnswerForQuestion:question usingKey:key result:^(NSString *result_) {
 | 
			
		||||
            setResult( result_ );
 | 
			
		||||
        }];
 | 
			
		||||
    } );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (void)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey result:(void ( ^ )(NSString *result))resultBlock {
 | 
			
		||||
- (void)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)key result:(void ( ^ )(NSString *result))resultBlock {
 | 
			
		||||
 | 
			
		||||
    NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
 | 
			
		||||
    NSAssert( [[key keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
 | 
			
		||||
    NSString *name = site.name;
 | 
			
		||||
    BOOL loginGenerated = site.loginGenerated && [[MPAppDelegate_Shared get] isFeatureUnlocked:MPProductGenerateLogins];
 | 
			
		||||
    NSString *loginName = site.loginName;
 | 
			
		||||
    id<MPAlgorithm> algorithm = nil;
 | 
			
		||||
    if (!name.length)
 | 
			
		||||
        err( @"Missing name." );
 | 
			
		||||
    else if (!siteKey)
 | 
			
		||||
    else if (!key)
 | 
			
		||||
        err( @"Missing key." );
 | 
			
		||||
    else
 | 
			
		||||
        algorithm = site.algorithm;
 | 
			
		||||
@@ -487,244 +458,139 @@ static NSOperationQueue *_mpwQueue = nil;
 | 
			
		||||
        resultBlock( loginName );
 | 
			
		||||
    else
 | 
			
		||||
        PearlNotMainQueue( ^{
 | 
			
		||||
            resultBlock( [algorithm generateLoginForSiteNamed:name usingKey:siteKey] );
 | 
			
		||||
            resultBlock( [algorithm mpwLoginForSiteNamed:name usingKey:key] );
 | 
			
		||||
        } );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (void)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey result:(void ( ^ )(NSString *result))resultBlock {
 | 
			
		||||
- (void)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)key result:(void ( ^ )(NSString *result))resultBlock {
 | 
			
		||||
 | 
			
		||||
    NSAssert( [[key keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
 | 
			
		||||
    NSString *name = site.name;
 | 
			
		||||
    MPResultType type = site.type;
 | 
			
		||||
    id<MPAlgorithm> algorithm = nil;
 | 
			
		||||
    if (!site.name.length)
 | 
			
		||||
        err( @"Missing name." );
 | 
			
		||||
    else if (!key)
 | 
			
		||||
        err( @"Missing key." );
 | 
			
		||||
    else
 | 
			
		||||
        algorithm = site.algorithm;
 | 
			
		||||
 | 
			
		||||
    NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
 | 
			
		||||
    switch (site.type) {
 | 
			
		||||
        case MPSiteTypeGeneratedMaximum:
 | 
			
		||||
        case MPSiteTypeGeneratedLong:
 | 
			
		||||
        case MPSiteTypeGeneratedMedium:
 | 
			
		||||
        case MPSiteTypeGeneratedBasic:
 | 
			
		||||
        case MPSiteTypeGeneratedShort:
 | 
			
		||||
        case MPSiteTypeGeneratedPIN:
 | 
			
		||||
        case MPSiteTypeGeneratedName:
 | 
			
		||||
        case MPSiteTypeGeneratedPhrase: {
 | 
			
		||||
        case MPResultTypeTemplateMaximum:
 | 
			
		||||
        case MPResultTypeTemplateLong:
 | 
			
		||||
        case MPResultTypeTemplateMedium:
 | 
			
		||||
        case MPResultTypeTemplateBasic:
 | 
			
		||||
        case MPResultTypeTemplateShort:
 | 
			
		||||
        case MPResultTypeTemplatePIN:
 | 
			
		||||
        case MPResultTypeTemplateName:
 | 
			
		||||
        case MPResultTypeTemplatePhrase: {
 | 
			
		||||
            if (![site isKindOfClass:[MPGeneratedSiteEntity class]]) {
 | 
			
		||||
                wrn( @"Site with generated type %lu is not an MPGeneratedSiteEntity, but a %@.",
 | 
			
		||||
                        (long)site.type, [site class] );
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            NSString *name = site.name;
 | 
			
		||||
            MPSiteType type = site.type;
 | 
			
		||||
            NSUInteger counter = ((MPGeneratedSiteEntity *)site).counter;
 | 
			
		||||
            id<MPAlgorithm> algorithm = nil;
 | 
			
		||||
            if (!site.name.length)
 | 
			
		||||
                err( @"Missing name." );
 | 
			
		||||
            else if (!siteKey)
 | 
			
		||||
                err( @"Missing key." );
 | 
			
		||||
            else
 | 
			
		||||
                algorithm = site.algorithm;
 | 
			
		||||
 | 
			
		||||
            PearlNotMainQueue( ^{
 | 
			
		||||
                resultBlock( [algorithm generatePasswordForSiteNamed:name ofType:type withCounter:counter usingKey:siteKey] );
 | 
			
		||||
                resultBlock( [algorithm mpwTemplateForSiteNamed:name ofType:type withCounter:counter usingKey:key] );
 | 
			
		||||
            } );
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        case MPSiteTypeStoredPersonal: {
 | 
			
		||||
        case MPResultTypeStatefulPersonal:
 | 
			
		||||
        case MPResultTypeStatefulDevice: {
 | 
			
		||||
            if (![site isKindOfClass:[MPStoredSiteEntity class]]) {
 | 
			
		||||
                wrn( @"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.",
 | 
			
		||||
                        (long)site.type, [site class] );
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            NSData *encryptedContent = ((MPStoredSiteEntity *)site).contentObject;
 | 
			
		||||
            NSDictionary *siteQuery = [self queryForSite:site];
 | 
			
		||||
            NSData *state = [PearlKeyChain dataOfItemForQuery:siteQuery];
 | 
			
		||||
            state = state?: ((MPStoredSiteEntity *)site).contentObject;
 | 
			
		||||
 | 
			
		||||
            PearlNotMainQueue( ^{
 | 
			
		||||
                resultBlock( [self decryptContent:encryptedContent usingKey:siteKey] );
 | 
			
		||||
            } );
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        case MPSiteTypeStoredDevicePrivate: {
 | 
			
		||||
            NSAssert( [site isKindOfClass:[MPStoredSiteEntity class]],
 | 
			
		||||
                    @"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.", (long)site.type,
 | 
			
		||||
                    [site class] );
 | 
			
		||||
 | 
			
		||||
            NSDictionary *siteQuery = [self queryForDevicePrivateSiteNamed:site.name];
 | 
			
		||||
            NSData *encryptedContent = [PearlKeyChain dataOfItemForQuery:siteQuery];
 | 
			
		||||
 | 
			
		||||
            PearlNotMainQueue( ^{
 | 
			
		||||
                resultBlock( [self decryptContent:encryptedContent usingKey:siteKey] );
 | 
			
		||||
                resultBlock( [algorithm mpwResultForSiteNamed:name ofType:type parameter:[state encodeBase64]
 | 
			
		||||
                                                  withCounter:MPCounterValueInitial variant:MPKeyPurposeAuthentication context:nil
 | 
			
		||||
                                                     usingKey:key] );
 | 
			
		||||
            } );
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (void)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey result:(void ( ^ )(NSString *result))resultBlock {
 | 
			
		||||
- (void)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)key result:(void ( ^ )(NSString *result))resultBlock {
 | 
			
		||||
 | 
			
		||||
    NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
 | 
			
		||||
    NSAssert( [[key keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
 | 
			
		||||
    NSString *name = site.name;
 | 
			
		||||
    id<MPAlgorithm> algorithm = nil;
 | 
			
		||||
    if (!site.name.length)
 | 
			
		||||
        err( @"Missing name." );
 | 
			
		||||
    else if (!siteKey)
 | 
			
		||||
    else if (!key)
 | 
			
		||||
        err( @"Missing key." );
 | 
			
		||||
    else
 | 
			
		||||
        algorithm = site.algorithm;
 | 
			
		||||
 | 
			
		||||
    PearlNotMainQueue( ^{
 | 
			
		||||
        resultBlock( [algorithm generateAnswerForSiteNamed:name onQuestion:nil usingKey:siteKey] );
 | 
			
		||||
        resultBlock( [algorithm mpwAnswerForSiteNamed:name onQuestion:nil usingKey:key] );
 | 
			
		||||
    } );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (void)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)siteKey
 | 
			
		||||
- (void)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)key
 | 
			
		||||
                          result:(void ( ^ )(NSString *result))resultBlock {
 | 
			
		||||
 | 
			
		||||
    NSAssert( [[siteKey keyIDForAlgorithm:question.site.user.algorithm] isEqualToData:question.site.user.keyID],
 | 
			
		||||
    NSAssert( [[key keyIDForAlgorithm:question.site.user.algorithm] isEqualToData:question.site.user.keyID],
 | 
			
		||||
            @"Site does not belong to current user." );
 | 
			
		||||
    NSString *name = question.site.name;
 | 
			
		||||
    NSString *keyword = question.keyword;
 | 
			
		||||
    id<MPAlgorithm> algorithm = nil;
 | 
			
		||||
    if (!name.length)
 | 
			
		||||
        err( @"Missing name." );
 | 
			
		||||
    else if (!siteKey)
 | 
			
		||||
    else if (!key)
 | 
			
		||||
        err( @"Missing key." );
 | 
			
		||||
    else if ([[MPAppDelegate_Shared get] isFeatureUnlocked:MPProductGenerateAnswers])
 | 
			
		||||
        algorithm = question.site.algorithm;
 | 
			
		||||
 | 
			
		||||
    PearlNotMainQueue( ^{
 | 
			
		||||
        resultBlock( [algorithm generateAnswerForSiteNamed:name onQuestion:keyword usingKey:siteKey] );
 | 
			
		||||
        resultBlock( [algorithm mpwAnswerForSiteNamed:name onQuestion:keyword usingKey:key] );
 | 
			
		||||
    } );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (void)importProtectedPassword:(NSString *)protectedContent protectedByKey:(MPKey *)importKey
 | 
			
		||||
                       intoSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
 | 
			
		||||
- (void)importPassword:(NSString *)cipherText protectedByKey:(MPKey *)importKey
 | 
			
		||||
              intoSite:(MPSiteEntity *)site usingKey:(MPKey *)key {
 | 
			
		||||
 | 
			
		||||
    NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
 | 
			
		||||
    switch (site.type) {
 | 
			
		||||
        case MPSiteTypeGeneratedMaximum:
 | 
			
		||||
        case MPSiteTypeGeneratedLong:
 | 
			
		||||
        case MPSiteTypeGeneratedMedium:
 | 
			
		||||
        case MPSiteTypeGeneratedBasic:
 | 
			
		||||
        case MPSiteTypeGeneratedShort:
 | 
			
		||||
        case MPSiteTypeGeneratedPIN:
 | 
			
		||||
        case MPSiteTypeGeneratedName:
 | 
			
		||||
        case MPSiteTypeGeneratedPhrase:
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
        case MPSiteTypeStoredPersonal: {
 | 
			
		||||
            if (![site isKindOfClass:[MPStoredSiteEntity class]]) {
 | 
			
		||||
                wrn( @"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.",
 | 
			
		||||
                        (long)site.type, [site class] );
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            if ([[importKey keyIDForAlgorithm:self] isEqualToData:[siteKey keyIDForAlgorithm:self]])
 | 
			
		||||
                ((MPStoredSiteEntity *)site).contentObject = [protectedContent decodeBase64];
 | 
			
		||||
 | 
			
		||||
            else {
 | 
			
		||||
                NSString *clearContent = [self decryptContent:[protectedContent decodeBase64] usingKey:importKey];
 | 
			
		||||
                [self importClearTextPassword:clearContent intoSite:site usingKey:siteKey];
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        case MPSiteTypeStoredDevicePrivate:
 | 
			
		||||
            break;
 | 
			
		||||
    NSAssert( [[key keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
 | 
			
		||||
    if (cipherText && cipherText.length && site.type & MPResultTypeClassStateful) {
 | 
			
		||||
        NSString *plainText = [self mpwResultForSiteNamed:site.name ofType:site.type parameter:cipherText
 | 
			
		||||
                                                 withCounter:MPCounterValueInitial variant:MPKeyPurposeAuthentication context:nil
 | 
			
		||||
                                                    usingKey:importKey];
 | 
			
		||||
        if (plainText)
 | 
			
		||||
            [self savePassword:plainText toSite:site usingKey:key];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (void)importClearTextPassword:(NSString *)clearContent intoSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
 | 
			
		||||
- (NSDictionary *)queryForSite:(MPSiteEntity *)site {
 | 
			
		||||
 | 
			
		||||
    NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
 | 
			
		||||
    switch (site.type) {
 | 
			
		||||
        case MPSiteTypeGeneratedMaximum:
 | 
			
		||||
        case MPSiteTypeGeneratedLong:
 | 
			
		||||
        case MPSiteTypeGeneratedMedium:
 | 
			
		||||
        case MPSiteTypeGeneratedBasic:
 | 
			
		||||
        case MPSiteTypeGeneratedShort:
 | 
			
		||||
        case MPSiteTypeGeneratedPIN:
 | 
			
		||||
        case MPSiteTypeGeneratedName:
 | 
			
		||||
        case MPSiteTypeGeneratedPhrase:
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
        case MPSiteTypeStoredPersonal: {
 | 
			
		||||
            [self savePassword:clearContent toSite:site usingKey:siteKey];
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        case MPSiteTypeStoredDevicePrivate:
 | 
			
		||||
            break;
 | 
			
		||||
    }
 | 
			
		||||
    return [PearlKeyChain createQueryForClass:kSecClassGenericPassword attributes:@{
 | 
			
		||||
            (__bridge id)kSecAttrService: site.type & MPSiteFeatureDevicePrivate? @"DevicePrivate": @"Private",
 | 
			
		||||
            (__bridge id)kSecAttrAccount: site.name
 | 
			
		||||
    }                                 matches:nil];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (NSString *)exportPasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
 | 
			
		||||
- (NSString *)exportPasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)key {
 | 
			
		||||
 | 
			
		||||
    NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
 | 
			
		||||
    if (!(site.type & MPSiteFeatureExportContent))
 | 
			
		||||
        return nil;
 | 
			
		||||
 | 
			
		||||
    NSString *result = nil;
 | 
			
		||||
    switch (site.type) {
 | 
			
		||||
        case MPSiteTypeGeneratedMaximum:
 | 
			
		||||
        case MPSiteTypeGeneratedLong:
 | 
			
		||||
        case MPSiteTypeGeneratedMedium:
 | 
			
		||||
        case MPSiteTypeGeneratedBasic:
 | 
			
		||||
        case MPSiteTypeGeneratedShort:
 | 
			
		||||
        case MPSiteTypeGeneratedPIN:
 | 
			
		||||
        case MPSiteTypeGeneratedName:
 | 
			
		||||
        case MPSiteTypeGeneratedPhrase: {
 | 
			
		||||
            result = nil;
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        case MPSiteTypeStoredPersonal: {
 | 
			
		||||
            if (![site isKindOfClass:[MPStoredSiteEntity class]]) {
 | 
			
		||||
                wrn( @"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.",
 | 
			
		||||
                        (long)site.type, [site class] );
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            result = [((MPStoredSiteEntity *)site).contentObject encodeBase64];
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        case MPSiteTypeStoredDevicePrivate: {
 | 
			
		||||
            result = nil;
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return result;
 | 
			
		||||
    NSDictionary *siteQuery = [self queryForSite:site];
 | 
			
		||||
    NSData *state = [PearlKeyChain dataOfItemForQuery:siteQuery];
 | 
			
		||||
    return [state?: ((MPStoredSiteEntity *)site).contentObject encodeBase64];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (BOOL)migrateExplicitly:(BOOL)explicit {
 | 
			
		||||
- (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordOfType:(MPResultType)type byAttacker:(MPAttacker)attacker {
 | 
			
		||||
 | 
			
		||||
    return NO;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (NSDictionary *)queryForDevicePrivateSiteNamed:(NSString *)name {
 | 
			
		||||
 | 
			
		||||
    return [PearlKeyChain createQueryForClass:kSecClassGenericPassword
 | 
			
		||||
                                   attributes:@{
 | 
			
		||||
                                           (__bridge id)kSecAttrService: @"DevicePrivate",
 | 
			
		||||
                                           (__bridge id)kSecAttrAccount: name
 | 
			
		||||
                                   }
 | 
			
		||||
                                      matches:nil];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (NSString *)decryptContent:(NSData *)encryptedContent usingKey:(MPKey *)key {
 | 
			
		||||
 | 
			
		||||
    if (!key)
 | 
			
		||||
        return nil;
 | 
			
		||||
    NSData *decryptedContent = nil;
 | 
			
		||||
    if ([encryptedContent length]) {
 | 
			
		||||
        NSData *encryptionKey = [key keyForAlgorithm:self trimmedLength:PearlCryptKeySize];
 | 
			
		||||
        decryptedContent = [encryptedContent decryptWithSymmetricKey:encryptionKey padding:YES];
 | 
			
		||||
    }
 | 
			
		||||
    if (!decryptedContent)
 | 
			
		||||
        return nil;
 | 
			
		||||
 | 
			
		||||
    return [[NSString alloc] initWithBytes:decryptedContent.bytes length:decryptedContent.length encoding:NSUTF8StringEncoding];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordOfType:(MPSiteType)type byAttacker:(MPAttacker)attacker {
 | 
			
		||||
 | 
			
		||||
    if (!(type & MPSiteTypeClassGenerated))
 | 
			
		||||
    if (!(type & MPResultTypeClassTemplate))
 | 
			
		||||
        return NO;
 | 
			
		||||
    size_t count = 0;
 | 
			
		||||
    const char **templates = mpw_templatesForType( type, &count );
 | 
			
		||||
 
 | 
			
		||||
@@ -33,7 +33,7 @@
 | 
			
		||||
        return NO;
 | 
			
		||||
 | 
			
		||||
    if (!explicit) {
 | 
			
		||||
        if (site.type & MPSiteTypeClassGenerated) {
 | 
			
		||||
        if (site.type & MPResultTypeClassTemplate) {
 | 
			
		||||
            // This migration requires explicit permission for types of the generated class.
 | 
			
		||||
            site.requiresExplicitMigration = YES;
 | 
			
		||||
            return NO;
 | 
			
		||||
 
 | 
			
		||||
@@ -33,7 +33,7 @@
 | 
			
		||||
        return NO;
 | 
			
		||||
 | 
			
		||||
    if (!explicit) {
 | 
			
		||||
        if (site.type & MPSiteTypeClassGenerated && site.name.length != [site.name dataUsingEncoding:NSUTF8StringEncoding].length) {
 | 
			
		||||
        if (site.type & MPResultTypeClassTemplate && site.name.length != [site.name dataUsingEncoding:NSUTF8StringEncoding].length) {
 | 
			
		||||
            // This migration requires explicit permission for types of the generated class.
 | 
			
		||||
            site.requiresExplicitMigration = YES;
 | 
			
		||||
            return NO;
 | 
			
		||||
 
 | 
			
		||||
@@ -33,7 +33,7 @@
 | 
			
		||||
        return NO;
 | 
			
		||||
 | 
			
		||||
    if (!explicit) {
 | 
			
		||||
        if (site.type & MPSiteTypeClassGenerated &&
 | 
			
		||||
        if (site.type & MPResultTypeClassTemplate &&
 | 
			
		||||
            site.user.name.length != [site.user.name dataUsingEncoding:NSUTF8StringEncoding].length) {
 | 
			
		||||
            // This migration requires explicit permission for types of the generated class.
 | 
			
		||||
            site.requiresExplicitMigration = YES;
 | 
			
		||||
 
 | 
			
		||||
@@ -248,9 +248,9 @@
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
    for (MPSiteEntity *site in user.sites) {
 | 
			
		||||
        if (site.type & MPSiteTypeClassStored) {
 | 
			
		||||
        if (site.type & MPResultTypeClassStateful) {
 | 
			
		||||
            NSString *content;
 | 
			
		||||
            while (!(content = [site.algorithm storedPasswordForSite:(MPStoredSiteEntity *)site usingKey:recoverKey])) {
 | 
			
		||||
            while (!(content = [site.algorithm resolvePasswordForSite:(MPStoredSiteEntity *)site usingKey:recoverKey])) {
 | 
			
		||||
                // Failed to decrypt site with the current recoveryKey.  Ask user for a new one to use.
 | 
			
		||||
                NSString *masterPassword = nil;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -20,14 +20,6 @@
 | 
			
		||||
 | 
			
		||||
#import "MPFixable.h"
 | 
			
		||||
 | 
			
		||||
typedef NS_ENUM( NSUInteger, MPImportResult ) {
 | 
			
		||||
    MPImportResultSuccess,
 | 
			
		||||
    MPImportResultCancelled,
 | 
			
		||||
    MPImportResultInvalidPassword,
 | 
			
		||||
    MPImportResultMalformedInput,
 | 
			
		||||
    MPImportResultInternalError,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@interface MPAppDelegate_Shared(Store)
 | 
			
		||||
 | 
			
		||||
+ (NSManagedObjectContext *)managedObjectContextForMainThreadIfReady;
 | 
			
		||||
@@ -42,10 +34,13 @@ typedef NS_ENUM( NSUInteger, MPImportResult ) {
 | 
			
		||||
 | 
			
		||||
/** @param completion The block to execute after adding the site, executed from the main thread with the new site in the main MOC. */
 | 
			
		||||
- (void)addSiteNamed:(NSString *)siteName completion:(void ( ^ )(MPSiteEntity *site, NSManagedObjectContext *context))completion;
 | 
			
		||||
- (MPSiteEntity *)changeSite:(MPSiteEntity *)site saveInContext:(NSManagedObjectContext *)context toType:(MPSiteType)type;
 | 
			
		||||
- (MPImportResult)importSites:(NSString *)importedSitesString
 | 
			
		||||
- (MPSiteEntity *)changeSite:(MPSiteEntity *)site saveInContext:(NSManagedObjectContext *)context toType:(MPResultType)type;
 | 
			
		||||
- (void)importSites:(NSString *)importData
 | 
			
		||||
            askImportPassword:(NSString *( ^ )(NSString *userName))importPassword
 | 
			
		||||
              askUserPassword:(NSString *( ^ )(NSString *userName, NSUInteger importCount, NSUInteger deleteCount))userPassword;
 | 
			
		||||
- (NSString *)exportSitesRevealPasswords:(BOOL)revealPasswords;
 | 
			
		||||
              askUserPassword:(NSString *( ^ )(NSString *userName))userPassword
 | 
			
		||||
                       result:(void ( ^ )(NSError *error))resultBlock;
 | 
			
		||||
- (void)exportSitesRevealPasswords:(BOOL)revealPasswords
 | 
			
		||||
                 askExportPassword:(NSString *( ^ )(NSString *userName))askImportPassword
 | 
			
		||||
                            result:(void ( ^ )(NSString *mpsites, NSError *error))resultBlock;
 | 
			
		||||
 | 
			
		||||
@end
 | 
			
		||||
 
 | 
			
		||||
@@ -18,6 +18,7 @@
 | 
			
		||||
 | 
			
		||||
#import "MPAppDelegate_Store.h"
 | 
			
		||||
#import "mpw-marshall.h"
 | 
			
		||||
#import "mpw-util.h"
 | 
			
		||||
 | 
			
		||||
#if TARGET_OS_IPHONE
 | 
			
		||||
#define STORE_OPTIONS NSPersistentStoreFileProtectionKey : NSFileProtectionComplete,
 | 
			
		||||
@@ -489,7 +490,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        MPSiteType type = activeUser.defaultType;
 | 
			
		||||
        MPResultType type = activeUser.defaultType;
 | 
			
		||||
        id<MPAlgorithm> algorithm = MPAlgorithmDefault;
 | 
			
		||||
        Class entityType = [algorithm classOfType:type];
 | 
			
		||||
 | 
			
		||||
@@ -506,7 +507,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
 | 
			
		||||
    }];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (MPSiteEntity *)changeSite:(MPSiteEntity *)site saveInContext:(NSManagedObjectContext *)context toType:(MPSiteType)type {
 | 
			
		||||
- (MPSiteEntity *)changeSite:(MPSiteEntity *)site saveInContext:(NSManagedObjectContext *)context toType:(MPResultType)type {
 | 
			
		||||
 | 
			
		||||
    if (site.type == type)
 | 
			
		||||
        return site;
 | 
			
		||||
@@ -539,328 +540,214 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
 | 
			
		||||
    return site;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (MPImportResult)importSites:(NSString *)importedSitesString
 | 
			
		||||
            askImportPassword:(NSString *( ^ )(NSString *userName))importPassword
 | 
			
		||||
              askUserPassword:(NSString *( ^ )(NSString *userName, NSUInteger importCount, NSUInteger deleteCount))userPassword {
 | 
			
		||||
- (void)importSites:(NSString *)importData
 | 
			
		||||
  askImportPassword:(NSString *( ^ )(NSString *userName))importPassword
 | 
			
		||||
    askUserPassword:(NSString *( ^ )(NSString *userName))userPassword
 | 
			
		||||
             result:(void ( ^ )(NSError *error))resultBlock {
 | 
			
		||||
 | 
			
		||||
    NSAssert( ![[NSThread currentThread] isMainThread], @"This method should not be invoked from the main thread." );
 | 
			
		||||
 | 
			
		||||
    __block MPImportResult result = MPImportResultCancelled;
 | 
			
		||||
    do {
 | 
			
		||||
        if ([MPAppDelegate_Shared managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) {
 | 
			
		||||
            result = [self importSites:importedSitesString askImportPassword:importPassword askUserPassword:userPassword
 | 
			
		||||
                         saveInContext:context];
 | 
			
		||||
            NSError *error = [self importSites:importData askImportPassword:importPassword askUserPassword:userPassword
 | 
			
		||||
                                 saveInContext:context];
 | 
			
		||||
            PearlMainQueue( ^{
 | 
			
		||||
                resultBlock( error );
 | 
			
		||||
            } );
 | 
			
		||||
        }])
 | 
			
		||||
            break;
 | 
			
		||||
        usleep( (useconds_t)(USEC_PER_SEC * 0.2) );
 | 
			
		||||
    } while (YES);
 | 
			
		||||
 | 
			
		||||
    return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (MPImportResult)importSites:(NSString *)importedSitesString
 | 
			
		||||
            askImportPassword:(NSString *( ^ )(NSString *userName))askImportPassword
 | 
			
		||||
              askUserPassword:(NSString *( ^ )(NSString *userName, NSUInteger importCount, NSUInteger deleteCount))askUserPassword
 | 
			
		||||
                saveInContext:(NSManagedObjectContext *)context {
 | 
			
		||||
- (NSError *)importSites:(NSString *)importData
 | 
			
		||||
       askImportPassword:(NSString *( ^ )(NSString *userName))askImportPassword
 | 
			
		||||
         askUserPassword:(NSString *( ^ )(NSString *userName))askUserPassword
 | 
			
		||||
           saveInContext:(NSManagedObjectContext *)context {
 | 
			
		||||
 | 
			
		||||
    // Compile patterns.
 | 
			
		||||
    static NSRegularExpression *headerPattern;
 | 
			
		||||
    static NSArray *sitePatterns;
 | 
			
		||||
    NSError *error = nil;
 | 
			
		||||
    if (!headerPattern) {
 | 
			
		||||
        headerPattern = [[NSRegularExpression alloc]
 | 
			
		||||
                initWithPattern:@"^#[[:space:]]*([^:]+): (.*)"
 | 
			
		||||
                        options:(NSRegularExpressionOptions)0 error:&error];
 | 
			
		||||
        if (error) {
 | 
			
		||||
            MPError( error, @"Error loading the header pattern." );
 | 
			
		||||
            return MPImportResultInternalError;
 | 
			
		||||
    // Read metadata for the import file.
 | 
			
		||||
    MPMarshallInfo *info = mpw_marshall_read_info( importData.UTF8String );
 | 
			
		||||
    if (info->format == MPMarshallFormatNone)
 | 
			
		||||
        return MPError( ([NSError errorWithDomain:MPErrorDomain code:MPErrorMarshallCode userInfo:@{
 | 
			
		||||
                @"type"                  : @(MPMarshallErrorFormat),
 | 
			
		||||
                NSLocalizedDescriptionKey: @"This is not a Master Password import file.",
 | 
			
		||||
        }]), @"While importing sites." );
 | 
			
		||||
 | 
			
		||||
    // Get master password for import file.
 | 
			
		||||
    MPKey *importKey;
 | 
			
		||||
    NSString *importMasterPassword;
 | 
			
		||||
    do {
 | 
			
		||||
        importMasterPassword = askImportPassword( @(info->fullName) );
 | 
			
		||||
        if (!importMasterPassword) {
 | 
			
		||||
            inf( @"Import cancelled." );
 | 
			
		||||
            mpw_marshal_info_free( info );
 | 
			
		||||
            return MPError( ([NSError errorWithDomain:NSCocoaErrorDomain code:NSUserCancelledError userInfo:nil]), @"" );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    if (!sitePatterns) {
 | 
			
		||||
        sitePatterns = @[
 | 
			
		||||
                [[NSRegularExpression alloc] // Format 0
 | 
			
		||||
                        initWithPattern:@"^([^ ]+) +([[:digit:]]+) +([[:digit:]]+)(:[[:digit:]]+)? +([^\t]+)\t(.*)"
 | 
			
		||||
                                options:(NSRegularExpressionOptions)0 error:&error],
 | 
			
		||||
                [[NSRegularExpression alloc] // Format 1
 | 
			
		||||
                        initWithPattern:@"^([^ ]+) +([[:digit:]]+) +([[:digit:]]+)(:[[:digit:]]+)?(:[[:digit:]]+)? +([^\t]*)\t *([^\t]+)\t(.*)"
 | 
			
		||||
                                options:(NSRegularExpressionOptions)0 error:&error]
 | 
			
		||||
        ];
 | 
			
		||||
        if (error) {
 | 
			
		||||
            MPError( error, @"Error loading the site patterns." );
 | 
			
		||||
            return MPImportResultInternalError;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        importKey = [[MPKey alloc] initForFullName:@(info->fullName) withMasterPassword:importMasterPassword];
 | 
			
		||||
    } while ([[[importKey keyIDForAlgorithm:MPAlgorithmForVersion( info->algorithm )] encodeHex]
 | 
			
		||||
                            caseInsensitiveCompare:@(info->keyID)] != NSOrderedSame);
 | 
			
		||||
 | 
			
		||||
    // Parse import data.
 | 
			
		||||
    inf( @"Importing sites." );
 | 
			
		||||
    NSUInteger importFormat = 0;
 | 
			
		||||
    __block MPUserEntity *user = nil;
 | 
			
		||||
    NSUInteger importAvatar = NSNotFound;
 | 
			
		||||
    NSData *importKeyID = nil;
 | 
			
		||||
    NSString *importBundleVersion = nil, *importUserName = nil;
 | 
			
		||||
    id<MPAlgorithm> importAlgorithm = nil;
 | 
			
		||||
    MPSiteType importDefaultType = (MPSiteType)0;
 | 
			
		||||
    BOOL headerStarted = NO, headerEnded = NO, clearText = NO;
 | 
			
		||||
    NSArray *importedSiteLines = [importedSitesString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
 | 
			
		||||
    NSMutableSet *sitesToDelete = [NSMutableSet set];
 | 
			
		||||
    NSMutableArray *importedSiteSites = [NSMutableArray arrayWithCapacity:[importedSiteLines count]];
 | 
			
		||||
    NSFetchRequest *siteFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPSiteEntity class] )];
 | 
			
		||||
    for (NSString *importedSiteLine in importedSiteLines) {
 | 
			
		||||
        if ([importedSiteLine hasPrefix:@"#"]) {
 | 
			
		||||
            // Comment or header
 | 
			
		||||
            if (!headerStarted) {
 | 
			
		||||
                if ([importedSiteLine isEqualToString:@"##"])
 | 
			
		||||
                    headerStarted = YES;
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            if (headerEnded)
 | 
			
		||||
                continue;
 | 
			
		||||
            if ([importedSiteLine isEqualToString:@"##"]) {
 | 
			
		||||
                headerEnded = YES;
 | 
			
		||||
                continue;
 | 
			
		||||
    MPMarshallError importError = { .type = MPMarshallSuccess };
 | 
			
		||||
    MPMarshalledUser *importUser = mpw_marshall_read( importData.UTF8String, info->format, importMasterPassword.UTF8String, &importError );
 | 
			
		||||
    mpw_marshal_info_free( info );
 | 
			
		||||
 | 
			
		||||
    @try {
 | 
			
		||||
        if (!importUser || importError.type != MPMarshallSuccess)
 | 
			
		||||
            return MPError( ([NSError errorWithDomain:MPErrorDomain code:MPErrorMarshallCode userInfo:@{
 | 
			
		||||
                    @"type"                  : @(importError.type),
 | 
			
		||||
                    NSLocalizedDescriptionKey: @(importError.description),
 | 
			
		||||
            }]), @"While importing sites." );
 | 
			
		||||
 | 
			
		||||
        // Find an existing user to update.
 | 
			
		||||
        NSError *error = nil;
 | 
			
		||||
        NSFetchRequest *userFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPUserEntity class] )];
 | 
			
		||||
        userFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@", @(importUser->fullName)];
 | 
			
		||||
        NSArray *users = [context executeFetchRequest:userFetchRequest error:&error];
 | 
			
		||||
        if (!users)
 | 
			
		||||
            return MPError( error, @"While looking for user: %@.", @(importUser->fullName) );
 | 
			
		||||
        if ([users count] > 1)
 | 
			
		||||
            return MPMakeError( @"While looking for user: %@, found more than one: %zu",
 | 
			
		||||
                    @(importUser->fullName), (size_t)[users count] );
 | 
			
		||||
 | 
			
		||||
        // Get master key for user.
 | 
			
		||||
        MPUserEntity *user = [users lastObject];
 | 
			
		||||
        MPKey *userKey = importKey;
 | 
			
		||||
        while (user && ![[userKey keyIDForAlgorithm:user.algorithm] isEqualToData:user.keyID]) {
 | 
			
		||||
            NSString *userMasterPassword = askUserPassword( user.name );
 | 
			
		||||
            if (!userMasterPassword) {
 | 
			
		||||
                inf( @"Import cancelled." );
 | 
			
		||||
                return MPError( ([NSError errorWithDomain:NSCocoaErrorDomain code:NSUserCancelledError userInfo:nil]), @"" );
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Header
 | 
			
		||||
            if ([headerPattern numberOfMatchesInString:importedSiteLine options:(NSMatchingOptions)0
 | 
			
		||||
                                                 range:NSMakeRange( 0, [importedSiteLine length] )] != 1) {
 | 
			
		||||
                err( @"Invalid header format in line: %@", importedSiteLine );
 | 
			
		||||
                return MPImportResultMalformedInput;
 | 
			
		||||
            }
 | 
			
		||||
            NSTextCheckingResult *headerSites = [[headerPattern matchesInString:importedSiteLine options:(NSMatchingOptions)0
 | 
			
		||||
                                                                          range:NSMakeRange( 0, [importedSiteLine length] )] lastObject];
 | 
			
		||||
            NSString *headerName = [importedSiteLine substringWithRange:[headerSites rangeAtIndex:1]];
 | 
			
		||||
            NSString *headerValue = [importedSiteLine substringWithRange:[headerSites rangeAtIndex:2]];
 | 
			
		||||
 | 
			
		||||
            if ([headerName isEqualToString:@"Format"]) {
 | 
			
		||||
                importFormat = (NSUInteger)[headerValue integerValue];
 | 
			
		||||
                if (importFormat >= [sitePatterns count]) {
 | 
			
		||||
                    err( @"Unsupported import format: %lu", (unsigned long)importFormat );
 | 
			
		||||
                    return MPImportResultInternalError;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (([headerName isEqualToString:@"User Name"] || [headerName isEqualToString:@"Full Name"]) && !importUserName) {
 | 
			
		||||
                importUserName = headerValue;
 | 
			
		||||
 | 
			
		||||
                NSFetchRequest *userFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPUserEntity class] )];
 | 
			
		||||
                userFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@", importUserName];
 | 
			
		||||
                NSArray *users = [context executeFetchRequest:userFetchRequest error:&error];
 | 
			
		||||
                if (!users) {
 | 
			
		||||
                    MPError( error, @"While looking for user: %@.", importUserName );
 | 
			
		||||
                    return MPImportResultInternalError;
 | 
			
		||||
                }
 | 
			
		||||
                if ([users count] > 1) {
 | 
			
		||||
                    err( @"While looking for user: %@, found more than one: %lu", importUserName, (unsigned long)[users count] );
 | 
			
		||||
                    return MPImportResultInternalError;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                user = [users lastObject];
 | 
			
		||||
                dbg( @"Existing user? %@", [user debugDescription] );
 | 
			
		||||
            }
 | 
			
		||||
            if ([headerName isEqualToString:@"Avatar"])
 | 
			
		||||
                importAvatar = (NSUInteger)[headerValue integerValue];
 | 
			
		||||
            if ([headerName isEqualToString:@"Key ID"])
 | 
			
		||||
                importKeyID = [headerValue decodeHex];
 | 
			
		||||
            if ([headerName isEqualToString:@"Version"]) {
 | 
			
		||||
                importBundleVersion = headerValue;
 | 
			
		||||
                importAlgorithm = MPAlgorithmDefaultForBundleVersion( importBundleVersion );
 | 
			
		||||
            }
 | 
			
		||||
            if ([headerName isEqualToString:@"Algorithm"])
 | 
			
		||||
                importAlgorithm = MPAlgorithmForVersion( (MPAlgorithmVersion)[headerValue integerValue] );
 | 
			
		||||
            if ([headerName isEqualToString:@"Default Type"])
 | 
			
		||||
                importDefaultType = (MPSiteType)[headerValue integerValue];
 | 
			
		||||
            if ([headerName isEqualToString:@"Passwords"]) {
 | 
			
		||||
                if ([headerValue isEqualToString:@"VISIBLE"])
 | 
			
		||||
                    clearText = YES;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
        if (!headerEnded)
 | 
			
		||||
            continue;
 | 
			
		||||
        if (![importUserName length])
 | 
			
		||||
            return MPImportResultMalformedInput;
 | 
			
		||||
        if (![importedSiteLine length])
 | 
			
		||||
            continue;
 | 
			
		||||
 | 
			
		||||
        // Site
 | 
			
		||||
        NSRegularExpression *sitePattern = sitePatterns[importFormat];
 | 
			
		||||
        if ([sitePattern numberOfMatchesInString:importedSiteLine options:(NSMatchingOptions)0
 | 
			
		||||
                                           range:NSMakeRange( 0, [importedSiteLine length] )] != 1) {
 | 
			
		||||
            err( @"Invalid site format in line: %@", importedSiteLine );
 | 
			
		||||
            return MPImportResultMalformedInput;
 | 
			
		||||
        }
 | 
			
		||||
        NSTextCheckingResult *siteElements = [[sitePattern matchesInString:importedSiteLine options:(NSMatchingOptions)0
 | 
			
		||||
                                                                     range:NSMakeRange( 0, [importedSiteLine length] )] lastObject];
 | 
			
		||||
        NSString *lastUsed, *uses, *type, *version, *counter, *siteName, *loginName, *exportContent;
 | 
			
		||||
        switch (importFormat) {
 | 
			
		||||
            case 0:
 | 
			
		||||
                lastUsed = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:1]];
 | 
			
		||||
                uses = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:2]];
 | 
			
		||||
                type = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:3]];
 | 
			
		||||
                version = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:4]];
 | 
			
		||||
                if ([version length])
 | 
			
		||||
                    version = [version substringFromIndex:1]; // Strip the leading colon.
 | 
			
		||||
                counter = @"";
 | 
			
		||||
                loginName = @"";
 | 
			
		||||
                siteName = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:5]];
 | 
			
		||||
                exportContent = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:6]];
 | 
			
		||||
                break;
 | 
			
		||||
            case 1:
 | 
			
		||||
                lastUsed = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:1]];
 | 
			
		||||
                uses = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:2]];
 | 
			
		||||
                type = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:3]];
 | 
			
		||||
                version = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:4]];
 | 
			
		||||
                if ([version length])
 | 
			
		||||
                    version = [version substringFromIndex:1]; // Strip the leading colon.
 | 
			
		||||
                counter = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:5]];
 | 
			
		||||
                if ([counter length])
 | 
			
		||||
                    counter = [counter substringFromIndex:1]; // Strip the leading colon.
 | 
			
		||||
                loginName = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:6]];
 | 
			
		||||
                siteName = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:7]];
 | 
			
		||||
                exportContent = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:8]];
 | 
			
		||||
                break;
 | 
			
		||||
            default:
 | 
			
		||||
                err( @"Unexpected import format: %lu", (unsigned long)importFormat );
 | 
			
		||||
                return MPImportResultInternalError;
 | 
			
		||||
            userKey = [[MPKey alloc] initForFullName:@(importUser->fullName) withMasterPassword:userMasterPassword];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Find existing site.
 | 
			
		||||
        if (user) {
 | 
			
		||||
            siteFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@ AND user == %@", siteName, user];
 | 
			
		||||
            NSArray *existingSites = [context executeFetchRequest:siteFetchRequest error:&error];
 | 
			
		||||
            if (!existingSites) {
 | 
			
		||||
                MPError( error, @"Lookup of existing sites failed for site: %@, user: %@.", siteName, user.userID );
 | 
			
		||||
                return MPImportResultInternalError;
 | 
			
		||||
            }
 | 
			
		||||
            if ([existingSites count]) {
 | 
			
		||||
                dbg( @"Existing sites: %@", existingSites );
 | 
			
		||||
                [sitesToDelete addObjectsFromArray:existingSites];
 | 
			
		||||
            }
 | 
			
		||||
        // Update or create user.
 | 
			
		||||
        if (!user) {
 | 
			
		||||
            user = [MPUserEntity insertNewObjectInContext:context];
 | 
			
		||||
            user.name = @(importUser->fullName);
 | 
			
		||||
        }
 | 
			
		||||
        [importedSiteSites addObject:@[ lastUsed, uses, type, version, counter, loginName, siteName, exportContent ]];
 | 
			
		||||
        dbg( @"Will import site: lastUsed=%@, uses=%@, type=%@, version=%@, counter=%@, loginName=%@, siteName=%@, exportContent=%@",
 | 
			
		||||
                lastUsed, uses, type, version, counter, loginName, siteName, exportContent );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Ask for confirmation to import these sites and the master password of the user.
 | 
			
		||||
    inf( @"Importing %lu sites, deleting %lu sites, for user: %@", (unsigned long)[importedSiteSites count],
 | 
			
		||||
            (unsigned long)[sitesToDelete count], [MPUserEntity idFor:importUserName] );
 | 
			
		||||
    NSString *userMasterPassword = askUserPassword( user? user.name: importUserName, [importedSiteSites count],
 | 
			
		||||
            [sitesToDelete count] );
 | 
			
		||||
    if (!userMasterPassword) {
 | 
			
		||||
        inf( @"Import cancelled." );
 | 
			
		||||
        return MPImportResultCancelled;
 | 
			
		||||
    }
 | 
			
		||||
    MPKey *userKey = [[MPKey alloc] initForFullName:user? user.name: importUserName withMasterPassword:userMasterPassword];
 | 
			
		||||
    if (user && ![[userKey keyIDForAlgorithm:user.algorithm] isEqualToData:user.keyID])
 | 
			
		||||
        return MPImportResultInvalidPassword;
 | 
			
		||||
    __block MPKey *importKey = userKey;
 | 
			
		||||
    if (importKeyID && ![[importKey keyIDForAlgorithm:importAlgorithm] isEqualToData:importKeyID])
 | 
			
		||||
        importKey = [[MPKey alloc] initForFullName:importUserName withMasterPassword:askImportPassword( importUserName )];
 | 
			
		||||
    if (importKeyID && ![[importKey keyIDForAlgorithm:importAlgorithm] isEqualToData:importKeyID])
 | 
			
		||||
        return MPImportResultInvalidPassword;
 | 
			
		||||
 | 
			
		||||
    // Delete existing sites.
 | 
			
		||||
    if (sitesToDelete.count)
 | 
			
		||||
        [sitesToDelete enumerateObjectsUsingBlock:^(id obj, BOOL *stop) {
 | 
			
		||||
            inf( @"Deleting site: %@, it will be replaced by an imported site.", [obj name] );
 | 
			
		||||
            [context deleteObject:obj];
 | 
			
		||||
        }];
 | 
			
		||||
 | 
			
		||||
    // Make sure there is a user.
 | 
			
		||||
    if (user) {
 | 
			
		||||
        if (importAvatar != NSNotFound)
 | 
			
		||||
            user.avatar = importAvatar;
 | 
			
		||||
        if (importDefaultType)
 | 
			
		||||
            user.defaultType = importDefaultType;
 | 
			
		||||
        dbg( @"Updating User: %@", [user debugDescription] );
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        user = [MPUserEntity insertNewObjectInContext:context];
 | 
			
		||||
        user.name = importUserName;
 | 
			
		||||
        user.algorithm = MPAlgorithmDefault;
 | 
			
		||||
        user.algorithm = MPAlgorithmForVersion( importUser->algorithm );
 | 
			
		||||
        user.keyID = [userKey keyIDForAlgorithm:user.algorithm];
 | 
			
		||||
        user.defaultType = importDefaultType?: user.algorithm.defaultType;
 | 
			
		||||
        if (importAvatar != NSNotFound)
 | 
			
		||||
            user.avatar = importAvatar;
 | 
			
		||||
        dbg( @"Created User: %@", [user debugDescription] );
 | 
			
		||||
    }
 | 
			
		||||
        user.avatar = importUser->avatar;
 | 
			
		||||
        user.defaultType = importUser->defaultType;
 | 
			
		||||
        user.lastUsed = [NSDate dateWithTimeIntervalSince1970:MAX( user.lastUsed.timeIntervalSince1970, importUser->lastUsed )];
 | 
			
		||||
        dbg( @"Importing user: %@", [user debugDescription] );
 | 
			
		||||
 | 
			
		||||
    // Import new sites.
 | 
			
		||||
    for (NSArray *siteElements in importedSiteSites) {
 | 
			
		||||
        NSDate *lastUsed = [[NSDateFormatter rfc3339DateFormatter] dateFromString:siteElements[0]];
 | 
			
		||||
        NSUInteger uses = (unsigned)[siteElements[1] integerValue];
 | 
			
		||||
        MPSiteType type = (MPSiteType)[siteElements[2] integerValue];
 | 
			
		||||
        MPAlgorithmVersion version = (MPAlgorithmVersion)[siteElements[3] integerValue];
 | 
			
		||||
        NSUInteger counter = [siteElements[4] length]? (unsigned)[siteElements[4] integerValue]: NSNotFound;
 | 
			
		||||
        NSString *loginName = [siteElements[5] length]? siteElements[5]: nil;
 | 
			
		||||
        NSString *siteName = siteElements[6];
 | 
			
		||||
        NSString *exportContent = siteElements[7];
 | 
			
		||||
        // Update or create sites.
 | 
			
		||||
        for (size_t s = 0; s < importUser->sites_count; ++s) {
 | 
			
		||||
            MPMarshalledSite *importSite = &importUser->sites[s];
 | 
			
		||||
 | 
			
		||||
        // Create new site.
 | 
			
		||||
        id<MPAlgorithm> algorithm = MPAlgorithmForVersion( version );
 | 
			
		||||
        Class entityType = [algorithm classOfType:type];
 | 
			
		||||
        if (!entityType) {
 | 
			
		||||
            err( @"Invalid site type in import file: %@ has type %lu", siteName, (long)type );
 | 
			
		||||
            return MPImportResultInternalError;
 | 
			
		||||
            // Find an existing site to update.
 | 
			
		||||
            NSFetchRequest *siteFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPSiteEntity class] )];
 | 
			
		||||
            siteFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@ AND user == %@", @(importSite->name), user];
 | 
			
		||||
            NSArray *existingSites = [context executeFetchRequest:siteFetchRequest error:&error];
 | 
			
		||||
            if (!existingSites)
 | 
			
		||||
                return MPError( error, @"Lookup of existing sites failed for site: %@, user: %@", @(importSite->name), user.userID );
 | 
			
		||||
            if ([existingSites count])
 | 
			
		||||
                // Update existing site.
 | 
			
		||||
                for (MPSiteEntity *site in existingSites) {
 | 
			
		||||
                    [self importSite:importSite protectedByKey:importKey intoSite:site usingKey:userKey];
 | 
			
		||||
                    dbg( @"Updated site: %@", [site debugDescription] );
 | 
			
		||||
                }
 | 
			
		||||
            else {
 | 
			
		||||
                // Create new site.
 | 
			
		||||
                id<MPAlgorithm> algorithm = MPAlgorithmForVersion( importSite->algorithm );
 | 
			
		||||
                Class entityType = [algorithm classOfType:importSite->type];
 | 
			
		||||
                if (!entityType)
 | 
			
		||||
                    return MPMakeError( @"Invalid site type in import file: %@ has type %lu", @(importSite->name), (long)importSite->type );
 | 
			
		||||
 | 
			
		||||
                MPSiteEntity *site = (MPSiteEntity *)[entityType insertNewObjectInContext:context];
 | 
			
		||||
                site.user = user;
 | 
			
		||||
 | 
			
		||||
                [self importSite:importSite protectedByKey:importKey intoSite:site usingKey:userKey];
 | 
			
		||||
                dbg( @"Created site: %@", [site debugDescription] );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        MPSiteEntity *site = (MPSiteEntity *)[entityType insertNewObjectInContext:context];
 | 
			
		||||
        site.name = siteName;
 | 
			
		||||
        site.loginName = loginName;
 | 
			
		||||
        site.user = user;
 | 
			
		||||
        site.type = type;
 | 
			
		||||
        site.uses = uses;
 | 
			
		||||
        site.lastUsed = lastUsed;
 | 
			
		||||
        site.algorithm = algorithm;
 | 
			
		||||
        if ([exportContent length]) {
 | 
			
		||||
            if (clearText)
 | 
			
		||||
                [site.algorithm importClearTextPassword:exportContent intoSite:site usingKey:userKey];
 | 
			
		||||
            else
 | 
			
		||||
                [site.algorithm importProtectedPassword:exportContent protectedByKey:importKey intoSite:site usingKey:userKey];
 | 
			
		||||
        }
 | 
			
		||||
        if ([site isKindOfClass:[MPGeneratedSiteEntity class]] && counter != NSNotFound)
 | 
			
		||||
            ((MPGeneratedSiteEntity *)site).counter = counter;
 | 
			
		||||
 | 
			
		||||
        dbg( @"Created Site: %@", [site debugDescription] );
 | 
			
		||||
        if (![context saveToStore])
 | 
			
		||||
            return MPMakeError( @"Failed saving imported changes." );
 | 
			
		||||
 | 
			
		||||
        inf( @"Import completed successfully." );
 | 
			
		||||
        [[NSNotificationCenter defaultCenter] postNotificationName:MPSitesImportedNotification object:nil userInfo:@{
 | 
			
		||||
                MPSitesImportedNotificationUserKey: user
 | 
			
		||||
        }];
 | 
			
		||||
        return nil;
 | 
			
		||||
    }
 | 
			
		||||
    @finally {
 | 
			
		||||
        mpw_marshal_free( importUser );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (![context saveToStore])
 | 
			
		||||
        return MPImportResultInternalError;
 | 
			
		||||
 | 
			
		||||
    inf( @"Import completed successfully." );
 | 
			
		||||
 | 
			
		||||
    [[NSNotificationCenter defaultCenter] postNotificationName:MPSitesImportedNotification object:nil userInfo:@{
 | 
			
		||||
            MPSitesImportedNotificationUserKey: user
 | 
			
		||||
    }];
 | 
			
		||||
 | 
			
		||||
    return MPImportResultSuccess;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (NSString *)exportSitesRevealPasswords:(BOOL)revealPasswords {
 | 
			
		||||
- (void)importSite:(const MPMarshalledSite *)importSite protectedByKey:(MPKey *)importKey intoSite:(MPSiteEntity *)site
 | 
			
		||||
          usingKey:(MPKey *)userKey {
 | 
			
		||||
 | 
			
		||||
    MPUserEntity *activeUser = [self activeUserForMainThread];
 | 
			
		||||
    inf( @"Exporting sites, %@, for user: %@", revealPasswords? @"revealing passwords": @"omitting passwords", activeUser.userID );
 | 
			
		||||
    site.name = @(importSite->name);
 | 
			
		||||
    if (importSite->content)
 | 
			
		||||
        [site.algorithm importPassword:@(importSite->content) protectedByKey:importKey intoSite:site usingKey:userKey];
 | 
			
		||||
    site.type = importSite->type;
 | 
			
		||||
    if ([site isKindOfClass:[MPGeneratedSiteEntity class]])
 | 
			
		||||
        ((MPGeneratedSiteEntity *)site).counter = importSite->counter;
 | 
			
		||||
    site.algorithm = MPAlgorithmForVersion( importSite->algorithm );
 | 
			
		||||
    site.loginName = importSite->loginName? @(importSite->loginName): nil;
 | 
			
		||||
    site.loginGenerated = importSite->loginGenerated;
 | 
			
		||||
    site.url = importSite->url? @(importSite->url): nil;
 | 
			
		||||
    site.uses = importSite->uses;
 | 
			
		||||
    site.lastUsed = [NSDate dateWithTimeIntervalSince1970:importSite->lastUsed];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    MPMarshalledUser exportUser = mpw_marshall_user( activeUser.name.UTF8String,
 | 
			
		||||
            [self.key keyForAlgorithm:activeUser.algorithm], activeUser.algorithm.version );
 | 
			
		||||
    exportUser.avatar = activeUser.avatar;
 | 
			
		||||
    exportUser.defaultType = activeUser.defaultType;
 | 
			
		||||
    exportUser.lastUsed = (time_t)activeUser.lastUsed.timeIntervalSince1970;
 | 
			
		||||
- (void)exportSitesRevealPasswords:(BOOL)revealPasswords
 | 
			
		||||
                 askExportPassword:(NSString *( ^ )(NSString *userName))askImportPassword
 | 
			
		||||
                            result:(void ( ^ )(NSString *mpsites, NSError *error))resultBlock {
 | 
			
		||||
 | 
			
		||||
    [MPAppDelegate_Shared managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
 | 
			
		||||
        MPUserEntity *user = [self activeUserInContext:context];
 | 
			
		||||
        NSString *masterPassword = askImportPassword( user.name );
 | 
			
		||||
 | 
			
		||||
    for (MPSiteEntity *site in activeUser.sites) {
 | 
			
		||||
        MPMarshalledSite exportSite = mpw_marshall_site( &exportUser,
 | 
			
		||||
                site.name.UTF8String, site.type, site.counter, site.algorithm.version );
 | 
			
		||||
        exportSite.loginName = site.loginName.UTF8String;
 | 
			
		||||
        exportSite.url = site.url.UTF8String;
 | 
			
		||||
        exportSite.uses = site.uses;
 | 
			
		||||
        exportSite.lastUsed = (time_t)site.lastUsed.timeIntervalSince1970;
 | 
			
		||||
        inf( @"Exporting sites, %@, for user: %@", revealPasswords? @"revealing passwords": @"omitting passwords", user.userID );
 | 
			
		||||
        MPMarshalledUser *exportUser = mpw_marshall_user( user.name.UTF8String, masterPassword.UTF8String, user.algorithm.version );
 | 
			
		||||
        exportUser->redacted = !revealPasswords;
 | 
			
		||||
        exportUser->avatar = (unsigned int)user.avatar;
 | 
			
		||||
        exportUser->defaultType = user.defaultType;
 | 
			
		||||
        exportUser->lastUsed = (time_t)user.lastUsed.timeIntervalSince1970;
 | 
			
		||||
 | 
			
		||||
        for (MPSiteQuestionEntity *siteQuestion in site.questions)
 | 
			
		||||
            mpw_marshal_question( &exportSite, siteQuestion.keyword.UTF8String );
 | 
			
		||||
    }
 | 
			
		||||
        for (MPSiteEntity *site in user.sites) {
 | 
			
		||||
            MPCounterValue counter = MPCounterValueInitial;
 | 
			
		||||
            if ([site isKindOfClass:[MPGeneratedSiteEntity class]])
 | 
			
		||||
                counter = ((MPGeneratedSiteEntity *)site).counter;
 | 
			
		||||
            NSString *content = revealPasswords
 | 
			
		||||
                                ? [site.algorithm exportPasswordForSite:site usingKey:self.key]
 | 
			
		||||
                                : [site.algorithm resolvePasswordForSite:site usingKey:self.key];
 | 
			
		||||
 | 
			
		||||
    mpw_marshall_write( &export, MPMarshallFormatFlat, exportUser );
 | 
			
		||||
            MPMarshalledSite *exportSite = mpw_marshall_site( exportUser,
 | 
			
		||||
                    site.name.UTF8String, site.type, counter, site.algorithm.version );
 | 
			
		||||
            exportSite->content = content.UTF8String;
 | 
			
		||||
            exportSite->loginName = site.loginName.UTF8String;
 | 
			
		||||
            exportSite->loginGenerated = site.loginGenerated;
 | 
			
		||||
            exportSite->url = site.url.UTF8String;
 | 
			
		||||
            exportSite->uses = (unsigned int)site.uses;
 | 
			
		||||
            exportSite->lastUsed = (time_t)site.lastUsed.timeIntervalSince1970;
 | 
			
		||||
 | 
			
		||||
            for (MPSiteQuestionEntity *siteQuestion in site.questions)
 | 
			
		||||
                mpw_marshal_question( exportSite, siteQuestion.keyword.UTF8String );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        char *export = NULL;
 | 
			
		||||
        MPMarshallError exportError = (MPMarshallError){ .type= MPMarshallSuccess };
 | 
			
		||||
        mpw_marshall_write( &export, MPMarshallFormatFlat, exportUser, &exportError );
 | 
			
		||||
        NSString *mpsites = nil;
 | 
			
		||||
        if (export && exportError.type == MPMarshallSuccess)
 | 
			
		||||
            mpsites = [NSString stringWithCString:export encoding:NSUTF8StringEncoding];
 | 
			
		||||
        mpw_free_string( export );
 | 
			
		||||
 | 
			
		||||
        resultBlock( mpsites, exportError.type == MPMarshallSuccess? nil:
 | 
			
		||||
                              [NSError errorWithDomain:MPErrorDomain code:MPErrorMarshallCode userInfo:@{
 | 
			
		||||
                                      @"type"                  : @(exportError.type),
 | 
			
		||||
                                      NSLocalizedDescriptionKey: @(exportError.description),
 | 
			
		||||
                              }] );
 | 
			
		||||
    }];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@end
 | 
			
		||||
 
 | 
			
		||||
@@ -48,7 +48,7 @@
 | 
			
		||||
@interface MPSiteEntity(MP)<MPFixable>
 | 
			
		||||
 | 
			
		||||
@property(assign) BOOL loginGenerated;
 | 
			
		||||
@property(assign) MPSiteType type;
 | 
			
		||||
@property(assign) MPResultType type;
 | 
			
		||||
@property(readonly) NSString *typeName;
 | 
			
		||||
@property(readonly) NSString *typeShortName;
 | 
			
		||||
@property(readonly) NSString *typeClassName;
 | 
			
		||||
@@ -71,7 +71,7 @@
 | 
			
		||||
 | 
			
		||||
@interface MPGeneratedSiteEntity(MP)
 | 
			
		||||
 | 
			
		||||
@property(assign) NSUInteger counter;
 | 
			
		||||
@property(assign) MPCounterValue counter;
 | 
			
		||||
 | 
			
		||||
@end
 | 
			
		||||
 | 
			
		||||
@@ -80,7 +80,7 @@
 | 
			
		||||
@property(assign) NSUInteger avatar;
 | 
			
		||||
@property(assign) BOOL saveKey;
 | 
			
		||||
@property(assign) BOOL touchID;
 | 
			
		||||
@property(assign) MPSiteType defaultType;
 | 
			
		||||
@property(assign) MPResultType defaultType;
 | 
			
		||||
@property(readonly) NSString *userID;
 | 
			
		||||
@property(strong) id<MPAlgorithm> algorithm;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -82,9 +82,9 @@
 | 
			
		||||
    return MPFixableResultNoProblems;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (MPSiteType)type {
 | 
			
		||||
- (MPResultType)type {
 | 
			
		||||
 | 
			
		||||
    return (MPSiteType)[self.type_ unsignedIntegerValue];
 | 
			
		||||
    return (MPResultType)[self.type_ unsignedIntegerValue];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (void)setLoginGenerated:(BOOL)aLoginGenerated {
 | 
			
		||||
@@ -97,7 +97,7 @@
 | 
			
		||||
    return [self.loginGenerated_ boolValue];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (void)setType:(MPSiteType)aType {
 | 
			
		||||
- (void)setType:(MPResultType)aType {
 | 
			
		||||
 | 
			
		||||
    self.type_ = @(aType);
 | 
			
		||||
}
 | 
			
		||||
@@ -251,7 +251,7 @@
 | 
			
		||||
 | 
			
		||||
    MPFixableResult result = [super findAndFixInconsistenciesInContext:context];
 | 
			
		||||
 | 
			
		||||
    if (!self.type || self.type == (MPSiteType)NSNotFound || ![[self.algorithm allTypes] containsObject:self.type_])
 | 
			
		||||
    if (!self.type || self.type == (MPResultType)NSNotFound || ![[self.algorithm allTypes] containsObject:self.type_])
 | 
			
		||||
        // Invalid self.type
 | 
			
		||||
        result = MPApplyFix( result, ^MPFixableResult {
 | 
			
		||||
            wrn( @"Invalid type for: %@ of %@, type: %ld.  Will use %ld instead.",
 | 
			
		||||
@@ -259,7 +259,7 @@
 | 
			
		||||
            self.type = self.user.defaultType;
 | 
			
		||||
            return MPFixableResultProblemsFixed;
 | 
			
		||||
        } );
 | 
			
		||||
    if (!self.type || self.type == (MPSiteType)NSNotFound || ![[self.algorithm allTypes] containsObject:self.type_])
 | 
			
		||||
    if (!self.type || self.type == (MPResultType)NSNotFound || ![[self.algorithm allTypes] containsObject:self.type_])
 | 
			
		||||
        // Invalid self.user.defaultType
 | 
			
		||||
        result = MPApplyFix( result, ^MPFixableResult {
 | 
			
		||||
            wrn( @"Invalid type for: %@ of %@, type: %ld.  Will use %ld instead.",
 | 
			
		||||
@@ -270,7 +270,7 @@
 | 
			
		||||
    if (![self isKindOfClass:[self.algorithm classOfType:self.type]])
 | 
			
		||||
        // Mismatch between self.type and self.class
 | 
			
		||||
        result = MPApplyFix( result, ^MPFixableResult {
 | 
			
		||||
            for (MPSiteType newType = self.type; self.type != (newType = [self.algorithm nextType:newType]);)
 | 
			
		||||
            for (MPResultType newType = self.type; self.type != (newType = [self.algorithm nextType:newType]);)
 | 
			
		||||
                if ([self isKindOfClass:[self.algorithm classOfType:newType]]) {
 | 
			
		||||
                    wrn( @"Mismatching type for: %@ of %@, type: %lu, class: %@.  Will use %ld instead.",
 | 
			
		||||
                            self.name, self.user.name, (long)self.type, self.class, (long)newType );
 | 
			
		||||
@@ -286,12 +286,12 @@
 | 
			
		||||
    return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (NSUInteger)counter {
 | 
			
		||||
- (MPCounterValue)counter {
 | 
			
		||||
 | 
			
		||||
    return [self.counter_ unsignedIntegerValue];
 | 
			
		||||
    return (MPCounterValue)[self.counter_ unsignedIntegerValue];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (void)setCounter:(NSUInteger)aCounter {
 | 
			
		||||
- (void)setCounter:(MPCounterValue)aCounter {
 | 
			
		||||
 | 
			
		||||
    self.counter_ = @(aCounter);
 | 
			
		||||
}
 | 
			
		||||
@@ -354,12 +354,12 @@
 | 
			
		||||
    self.touchID_ = @(aTouchID);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (MPSiteType)defaultType {
 | 
			
		||||
- (MPResultType)defaultType {
 | 
			
		||||
 | 
			
		||||
    return (MPSiteType)[self.defaultType_ unsignedIntegerValue]?: self.algorithm.defaultType;
 | 
			
		||||
    return (MPResultType)[self.defaultType_ unsignedIntegerValue]?: self.algorithm.defaultType;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (void)setDefaultType:(MPSiteType)aDefaultType {
 | 
			
		||||
- (void)setDefaultType:(MPResultType)aDefaultType {
 | 
			
		||||
 | 
			
		||||
    self.defaultType_ = @(aDefaultType);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -22,6 +22,7 @@
 | 
			
		||||
__BEGIN_DECLS
 | 
			
		||||
extern NSString *const MPErrorDomain;
 | 
			
		||||
extern NSInteger const MPErrorHangCode;
 | 
			
		||||
extern NSInteger const MPErrorMarshallCode;
 | 
			
		||||
 | 
			
		||||
extern NSString *const MPSignedInNotification;
 | 
			
		||||
extern NSString *const MPSignedOutNotification;
 | 
			
		||||
@@ -38,13 +39,20 @@ __END_DECLS
 | 
			
		||||
 | 
			
		||||
#ifdef CRASHLYTICS
 | 
			
		||||
#define MPError(error_, message, ...) ({ \
 | 
			
		||||
    err( message @"%@%@", ##__VA_ARGS__, error_? @"\n": @"", [error_ fullDescription]?: @"" ); \
 | 
			
		||||
    NSError *error = error_; \
 | 
			
		||||
    err( message @"%@%@", ##__VA_ARGS__, error && [message length]? @"\n": @"", [error fullDescription]?: @"" ); \
 | 
			
		||||
    \
 | 
			
		||||
    if ([[MPConfig get].sendInfo boolValue]) { \
 | 
			
		||||
        [[Crashlytics sharedInstance] recordError:error_ withAdditionalUserInfo:@{ \
 | 
			
		||||
        [[Crashlytics sharedInstance] recordError:error withAdditionalUserInfo:@{ \
 | 
			
		||||
                @"location": strf( @"%@:%d %@", @(basename((char *)__FILE__)), __LINE__, NSStringFromSelector(_cmd) ), \
 | 
			
		||||
        }]; \
 | 
			
		||||
    } \
 | 
			
		||||
    error; \
 | 
			
		||||
})
 | 
			
		||||
#define MPMakeError(message, ...) ({ \
 | 
			
		||||
     MPError( [NSError errorWithDomain:MPErrorDomain code:0 userInfo:@{ \
 | 
			
		||||
        NSLocalizedDescriptionKey: strf( message, ##__VA_ARGS__ ) \
 | 
			
		||||
     }], @"" ); \
 | 
			
		||||
})
 | 
			
		||||
#else
 | 
			
		||||
#define MPError(error_, message, ...) ({ \
 | 
			
		||||
 
 | 
			
		||||
@@ -20,6 +20,7 @@
 | 
			
		||||
 | 
			
		||||
NSString *const MPErrorDomain = @"MPErrorDomain";
 | 
			
		||||
NSInteger const MPErrorHangCode = 1;
 | 
			
		||||
NSInteger const MPErrorMarshallCode = 1;
 | 
			
		||||
 | 
			
		||||
NSString *const MPSignedInNotification = @"MPSignedInNotification";
 | 
			
		||||
NSString *const MPSignedOutNotification = @"MPSignedOutNotification";
 | 
			
		||||
 
 | 
			
		||||
@@ -233,14 +233,14 @@
 | 
			
		||||
    else
 | 
			
		||||
        PearlNotMainQueue( ^{
 | 
			
		||||
            [self updatePasswordWithResult:
 | 
			
		||||
                    [self.algorithm generatePasswordForSiteNamed:self.name ofType:self.type withCounter:self.counter
 | 
			
		||||
                                                        usingKey:[MPAppDelegate_Shared get].key]];
 | 
			
		||||
                    [self.algorithm mpwTemplateForSiteNamed:self.name ofType:self.type withCounter:self.counter
 | 
			
		||||
                                                   usingKey:[MPAppDelegate_Shared get].key]];
 | 
			
		||||
            [self updateLoginNameWithResult:
 | 
			
		||||
                    [self.algorithm generateLoginForSiteNamed:self.name
 | 
			
		||||
                                                     usingKey:[MPAppDelegate_Shared get].key]];
 | 
			
		||||
                    [self.algorithm mpwLoginForSiteNamed:self.name
 | 
			
		||||
                                                usingKey:[MPAppDelegate_Shared get].key]];
 | 
			
		||||
            [self updateAnswerWithResult:
 | 
			
		||||
                    [self.algorithm generateAnswerForSiteNamed:self.name onQuestion:self.question
 | 
			
		||||
                                                      usingKey:[MPAppDelegate_Shared get].key]];
 | 
			
		||||
                    [self.algorithm mpwAnswerForSiteNamed:self.name onQuestion:self.question
 | 
			
		||||
                                                 usingKey:[MPAppDelegate_Shared get].key]];
 | 
			
		||||
        } );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -252,8 +252,8 @@
 | 
			
		||||
    [entity resolveLoginUsingKey:[MPAppDelegate_Shared get].key result:^(NSString *result) {
 | 
			
		||||
        [self updateLoginNameWithResult:result];
 | 
			
		||||
    }];
 | 
			
		||||
    [self updateAnswerWithResult:[self.algorithm generateAnswerForSiteNamed:self.name onQuestion:self.question
 | 
			
		||||
                                                                   usingKey:[MPAppDelegate_Shared get].key]];
 | 
			
		||||
    [self updateAnswerWithResult:[self.algorithm mpwAnswerForSiteNamed:self.name onQuestion:self.question
 | 
			
		||||
                                                              usingKey:[MPAppDelegate_Shared get].key]];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (void)updatePasswordWithResult:(NSString *)result {
 | 
			
		||||
 
 | 
			
		||||
@@ -376,8 +376,8 @@
 | 
			
		||||
        MPSiteType type = (MPSiteType)[types[t] unsignedIntegerValue];
 | 
			
		||||
        NSString *title = [site.algorithm nameOfType:type];
 | 
			
		||||
        if (type & MPSiteTypeClassGenerated)
 | 
			
		||||
            title = strf( @"%@ – %@", [site.algorithm generatePasswordForSiteNamed:site.name ofType:type withCounter:site.counter
 | 
			
		||||
                                                                          usingKey:[MPMacAppDelegate get].key], title );
 | 
			
		||||
            title = strf( @"%@ – %@", [site.algorithm mpwTemplateForSiteNamed:site.name ofType:type withCounter:site.counter
 | 
			
		||||
                                                                     usingKey:[MPMacAppDelegate get].key], title );
 | 
			
		||||
 | 
			
		||||
        NSButtonCell *cell = [self.passwordTypesMatrix cellAtRow:(NSInteger)t column:0];
 | 
			
		||||
        cell.tag = type;
 | 
			
		||||
 
 | 
			
		||||
@@ -137,7 +137,7 @@
 | 
			
		||||
- (void)updatePassword {
 | 
			
		||||
 | 
			
		||||
    NSString *siteName = self.siteField.text;
 | 
			
		||||
    MPSiteType siteType = [self siteType];
 | 
			
		||||
    MPResultType siteType = [self siteType];
 | 
			
		||||
    NSUInteger siteCounter = (NSUInteger)self.counterStepper.value;
 | 
			
		||||
    self.counterLabel.text = strf( @"%lu", (unsigned long)siteCounter );
 | 
			
		||||
 | 
			
		||||
@@ -147,8 +147,8 @@
 | 
			
		||||
    [self.emergencyPasswordQueue addOperationWithBlock:^{
 | 
			
		||||
        NSString *sitePassword = nil;
 | 
			
		||||
        if (self.key && [siteName length])
 | 
			
		||||
            sitePassword = [MPAlgorithmDefault generatePasswordForSiteNamed:siteName ofType:siteType withCounter:siteCounter
 | 
			
		||||
                                                                   usingKey:self.key];
 | 
			
		||||
            sitePassword = [MPAlgorithmDefault mpwTemplateForSiteNamed:siteName ofType:siteType withCounter:siteCounter
 | 
			
		||||
                                                              usingKey:self.key];
 | 
			
		||||
 | 
			
		||||
        PearlMainQueue( ^{
 | 
			
		||||
            [self.activity stopAnimating];
 | 
			
		||||
@@ -157,21 +157,21 @@
 | 
			
		||||
    }];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (enum MPSiteType)siteType {
 | 
			
		||||
- (MPResultType)siteType {
 | 
			
		||||
 | 
			
		||||
    switch (self.typeControl.selectedSegmentIndex) {
 | 
			
		||||
        case 0:
 | 
			
		||||
            return MPSiteTypeGeneratedMaximum;
 | 
			
		||||
            return MPResultTypeTemplateMaximum;
 | 
			
		||||
        case 1:
 | 
			
		||||
            return MPSiteTypeGeneratedLong;
 | 
			
		||||
            return MPResultTypeTemplateLong;
 | 
			
		||||
        case 2:
 | 
			
		||||
            return MPSiteTypeGeneratedMedium;
 | 
			
		||||
            return MPResultTypeTemplateMedium;
 | 
			
		||||
        case 3:
 | 
			
		||||
            return MPSiteTypeGeneratedBasic;
 | 
			
		||||
            return MPResultTypeTemplateBasic;
 | 
			
		||||
        case 4:
 | 
			
		||||
            return MPSiteTypeGeneratedShort;
 | 
			
		||||
            return MPResultTypeTemplateShort;
 | 
			
		||||
        case 5:
 | 
			
		||||
            return MPSiteTypeGeneratedPIN;
 | 
			
		||||
            return MPResultTypeTemplatePIN;
 | 
			
		||||
        default:
 | 
			
		||||
            Throw( @"Unsupported type index: %ld", (long)self.typeControl.selectedSegmentIndex );
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -60,15 +60,15 @@
 | 
			
		||||
    self.touchIDSwitch.on = activeUser.touchID;
 | 
			
		||||
    self.touchIDSwitch.enabled = self.savePasswordSwitch.on && [[MPiOSAppDelegate get] isFeatureUnlocked:MPProductTouchID];
 | 
			
		||||
 | 
			
		||||
    MPSiteType defaultType = activeUser.defaultType;
 | 
			
		||||
    MPResultType defaultType = activeUser.defaultType;
 | 
			
		||||
    self.generated1TypeControl.selectedSegmentIndex = [self generated1SegmentIndexForType:defaultType];
 | 
			
		||||
    self.generated2TypeControl.selectedSegmentIndex = [self generated2SegmentIndexForType:defaultType];
 | 
			
		||||
    self.storedTypeControl.selectedSegmentIndex = [self storedSegmentIndexForType:defaultType];
 | 
			
		||||
    PearlNotMainQueue( ^{
 | 
			
		||||
        NSString *examplePassword = nil;
 | 
			
		||||
        if (defaultType & MPSiteTypeClassGenerated)
 | 
			
		||||
            examplePassword = [MPAlgorithmDefault generatePasswordForSiteNamed:@"test" ofType:defaultType
 | 
			
		||||
                                                                   withCounter:1 usingKey:[MPiOSAppDelegate get].key];
 | 
			
		||||
        if (defaultType & MPResultTypeClassTemplate)
 | 
			
		||||
            examplePassword = [MPAlgorithmDefault mpwTemplateForSiteNamed:@"test" ofType:defaultType
 | 
			
		||||
                                                              withCounter:1 usingKey:[MPiOSAppDelegate get].key];
 | 
			
		||||
        PearlMainQueue( ^{
 | 
			
		||||
            self.typeSamplePassword.text = [examplePassword length]? [NSString stringWithFormat:@"eg. %@", examplePassword]: nil;
 | 
			
		||||
        } );
 | 
			
		||||
@@ -164,7 +164,7 @@
 | 
			
		||||
        if (sender != self.storedTypeControl)
 | 
			
		||||
            self.storedTypeControl.selectedSegmentIndex = -1;
 | 
			
		||||
 | 
			
		||||
        MPSiteType defaultType = [self typeForSelectedSegment];
 | 
			
		||||
        MPResultType defaultType = [self typeForSelectedSegment];
 | 
			
		||||
        [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
 | 
			
		||||
            [[MPiOSAppDelegate get] activeUserInContext:context].defaultType = defaultType;
 | 
			
		||||
            [context saveToStore];
 | 
			
		||||
@@ -242,7 +242,7 @@
 | 
			
		||||
    return nil;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (MPSiteType)typeForSelectedSegment {
 | 
			
		||||
- (MPResultType)typeForSelectedSegment {
 | 
			
		||||
 | 
			
		||||
    NSInteger selectedGenerated1Index = self.generated1TypeControl.selectedSegmentIndex;
 | 
			
		||||
    NSInteger selectedGenerated2Index = self.generated2TypeControl.selectedSegmentIndex;
 | 
			
		||||
@@ -250,30 +250,30 @@
 | 
			
		||||
 | 
			
		||||
    switch (selectedGenerated1Index) {
 | 
			
		||||
        case 0:
 | 
			
		||||
            return MPSiteTypeGeneratedPhrase;
 | 
			
		||||
            return MPResultTypeTemplatePhrase;
 | 
			
		||||
        case 1:
 | 
			
		||||
            return MPSiteTypeGeneratedName;
 | 
			
		||||
            return MPResultTypeTemplateName;
 | 
			
		||||
        default:
 | 
			
		||||
            switch (selectedGenerated2Index) {
 | 
			
		||||
                case 0:
 | 
			
		||||
                    return MPSiteTypeGeneratedMaximum;
 | 
			
		||||
                    return MPResultTypeTemplateMaximum;
 | 
			
		||||
                case 1:
 | 
			
		||||
                    return MPSiteTypeGeneratedLong;
 | 
			
		||||
                    return MPResultTypeTemplateLong;
 | 
			
		||||
                case 2:
 | 
			
		||||
                    return MPSiteTypeGeneratedMedium;
 | 
			
		||||
                    return MPResultTypeTemplateMedium;
 | 
			
		||||
                case 3:
 | 
			
		||||
                    return MPSiteTypeGeneratedBasic;
 | 
			
		||||
                    return MPResultTypeTemplateBasic;
 | 
			
		||||
                case 4:
 | 
			
		||||
                    return MPSiteTypeGeneratedShort;
 | 
			
		||||
                    return MPResultTypeTemplateShort;
 | 
			
		||||
                case 5:
 | 
			
		||||
                    return MPSiteTypeGeneratedPIN;
 | 
			
		||||
                    return MPResultTypeTemplatePIN;
 | 
			
		||||
                default:
 | 
			
		||||
 | 
			
		||||
                    switch (selectedStoredIndex) {
 | 
			
		||||
                        case 0:
 | 
			
		||||
                            return MPSiteTypeStoredPersonal;
 | 
			
		||||
                            return MPResultTypeStatefulPersonal;
 | 
			
		||||
                        case 1:
 | 
			
		||||
                            return MPSiteTypeStoredDevicePrivate;
 | 
			
		||||
                            return MPResultTypeStatefulDevice;
 | 
			
		||||
                        default:
 | 
			
		||||
                            Throw( @"unsupported selected type index: generated1=%ld, generated2=%ld, stored=%ld",
 | 
			
		||||
                                    (long)selectedGenerated1Index, (long)selectedGenerated2Index, (long)selectedStoredIndex );
 | 
			
		||||
@@ -282,44 +282,44 @@
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (NSInteger)generated1SegmentIndexForType:(MPSiteType)type {
 | 
			
		||||
- (NSInteger)generated1SegmentIndexForType:(MPResultType)type {
 | 
			
		||||
 | 
			
		||||
    switch (type) {
 | 
			
		||||
        case MPSiteTypeGeneratedPhrase:
 | 
			
		||||
        case MPResultTypeTemplatePhrase:
 | 
			
		||||
            return 0;
 | 
			
		||||
        case MPSiteTypeGeneratedName:
 | 
			
		||||
        case MPResultTypeTemplateName:
 | 
			
		||||
            return 1;
 | 
			
		||||
        default:
 | 
			
		||||
            return -1;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (NSInteger)generated2SegmentIndexForType:(MPSiteType)type {
 | 
			
		||||
- (NSInteger)generated2SegmentIndexForType:(MPResultType)type {
 | 
			
		||||
 | 
			
		||||
    switch (type) {
 | 
			
		||||
        case MPSiteTypeGeneratedMaximum:
 | 
			
		||||
        case MPResultTypeTemplateMaximum:
 | 
			
		||||
            return 0;
 | 
			
		||||
        case MPSiteTypeGeneratedLong:
 | 
			
		||||
        case MPResultTypeTemplateLong:
 | 
			
		||||
            return 1;
 | 
			
		||||
        case MPSiteTypeGeneratedMedium:
 | 
			
		||||
        case MPResultTypeTemplateMedium:
 | 
			
		||||
            return 2;
 | 
			
		||||
        case MPSiteTypeGeneratedBasic:
 | 
			
		||||
        case MPResultTypeTemplateBasic:
 | 
			
		||||
            return 3;
 | 
			
		||||
        case MPSiteTypeGeneratedShort:
 | 
			
		||||
        case MPResultTypeTemplateShort:
 | 
			
		||||
            return 4;
 | 
			
		||||
        case MPSiteTypeGeneratedPIN:
 | 
			
		||||
        case MPResultTypeTemplatePIN:
 | 
			
		||||
            return 5;
 | 
			
		||||
        default:
 | 
			
		||||
            return -1;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (NSInteger)storedSegmentIndexForType:(MPSiteType)type {
 | 
			
		||||
- (NSInteger)storedSegmentIndexForType:(MPResultType)type {
 | 
			
		||||
 | 
			
		||||
    switch (type) {
 | 
			
		||||
        case MPSiteTypeStoredPersonal:
 | 
			
		||||
        case MPResultTypeStatefulPersonal:
 | 
			
		||||
            return 0;
 | 
			
		||||
        case MPSiteTypeStoredDevicePrivate:
 | 
			
		||||
        case MPResultTypeStatefulDevice:
 | 
			
		||||
            return 1;
 | 
			
		||||
        default:
 | 
			
		||||
            return -1;
 | 
			
		||||
 
 | 
			
		||||
@@ -284,7 +284,7 @@
 | 
			
		||||
    [PearlSheet showSheetWithTitle:@"Change Password Type" viewStyle:UIActionSheetStyleAutomatic
 | 
			
		||||
                         initSheet:^(UIActionSheet *sheet) {
 | 
			
		||||
                             for (NSNumber *typeNumber in [mainSite.algorithm allTypes]) {
 | 
			
		||||
                                 MPSiteType type = (MPSiteType)[typeNumber unsignedIntegerValue];
 | 
			
		||||
                                 MPResultType type = (MPResultType)[typeNumber unsignedIntegerValue];
 | 
			
		||||
                                 NSString *typeName = [mainSite.algorithm nameOfType:type];
 | 
			
		||||
                                 if (type == mainSite.type)
 | 
			
		||||
                                     [sheet addButtonWithTitle:strf( @"● %@", typeName )];
 | 
			
		||||
@@ -295,7 +295,7 @@
 | 
			
		||||
                if (buttonIndex == [sheet cancelButtonIndex])
 | 
			
		||||
                    return;
 | 
			
		||||
 | 
			
		||||
                MPSiteType type = (MPSiteType)[[mainSite.algorithm allTypes][buttonIndex] unsignedIntegerValue]?:
 | 
			
		||||
                MPResultType type = (MPResultType)[[mainSite.algorithm allTypes][buttonIndex] unsignedIntegerValue]?:
 | 
			
		||||
                                  mainSite.user.defaultType?: mainSite.algorithm.defaultType;
 | 
			
		||||
 | 
			
		||||
                [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
 | 
			
		||||
@@ -311,7 +311,7 @@
 | 
			
		||||
    self.loginNameField.enabled = YES;
 | 
			
		||||
    self.passwordField.enabled = YES;
 | 
			
		||||
 | 
			
		||||
    if ([self siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]].type & MPSiteTypeClassStored)
 | 
			
		||||
    if ([self siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]].type & MPResultTypeClassStateful)
 | 
			
		||||
        [self.passwordField becomeFirstResponder];
 | 
			
		||||
    else
 | 
			
		||||
        [self.loginNameField becomeFirstResponder];
 | 
			
		||||
@@ -537,7 +537,7 @@
 | 
			
		||||
        self.loginNameContainer.visible = settingsMode || mainSite.loginGenerated || [mainSite.loginName length];
 | 
			
		||||
        self.modeButton.visible = !self.transientSite;
 | 
			
		||||
        self.modeButton.alpha = settingsMode? 0.5f: 0.1f;
 | 
			
		||||
        self.counterLabel.visible = self.counterButton.visible = mainSite.type & MPSiteTypeClassGenerated;
 | 
			
		||||
        self.counterLabel.visible = self.counterButton.visible = mainSite.type & MPResultTypeClassTemplate;
 | 
			
		||||
        self.modeButton.selected = settingsMode;
 | 
			
		||||
        self.strengthLabel.gone = !settingsMode;
 | 
			
		||||
        self.modeScrollView.scrollEnabled = !self.transientSite;
 | 
			
		||||
@@ -565,8 +565,8 @@
 | 
			
		||||
        // Site Password
 | 
			
		||||
        self.passwordField.secureTextEntry = [[MPiOSConfig get].hidePasswords boolValue];
 | 
			
		||||
        self.passwordField.attributedPlaceholder = stra(
 | 
			
		||||
                mainSite.type & MPSiteTypeClassStored? strl( @"No password" ):
 | 
			
		||||
                mainSite.type & MPSiteTypeClassGenerated? strl( @"..." ): @"", @{
 | 
			
		||||
                mainSite.type & MPResultTypeClassStateful? strl( @"No password" ):
 | 
			
		||||
                mainSite.type & MPResultTypeClassTemplate? strl( @"..." ): @"", @{
 | 
			
		||||
                        NSForegroundColorAttributeName: [UIColor whiteColor]
 | 
			
		||||
                } );
 | 
			
		||||
 | 
			
		||||
@@ -585,10 +585,10 @@
 | 
			
		||||
 | 
			
		||||
            BOOL loginGenerated = site.loginGenerated;
 | 
			
		||||
            NSString *password = nil, *loginName = [site resolveLoginUsingKey:key];
 | 
			
		||||
            MPSiteType transientType = [[MPiOSAppDelegate get] activeUserInContext:context].defaultType?: MPAlgorithmDefault.defaultType;
 | 
			
		||||
            if (self.transientSite && transientType & MPSiteTypeClassGenerated)
 | 
			
		||||
                password = [MPAlgorithmDefault generatePasswordForSiteNamed:self.transientSite ofType:transientType
 | 
			
		||||
                                                                withCounter:1 usingKey:key];
 | 
			
		||||
            MPResultType transientType = [[MPiOSAppDelegate get] activeUserInContext:context].defaultType?: MPAlgorithmDefault.defaultType;
 | 
			
		||||
            if (self.transientSite && transientType & MPResultTypeClassTemplate)
 | 
			
		||||
                password = [MPAlgorithmDefault mpwTemplateForSiteNamed:self.transientSite ofType:transientType
 | 
			
		||||
                                                           withCounter:1 usingKey:key];
 | 
			
		||||
            else if (site)
 | 
			
		||||
                password = [site resolvePasswordUsingKey:key];
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -23,8 +23,8 @@
 | 
			
		||||
@protocol MPTypeDelegate<NSObject>
 | 
			
		||||
 | 
			
		||||
@required
 | 
			
		||||
- (void)didSelectType:(MPSiteType)type;
 | 
			
		||||
- (MPSiteType)selectedType;
 | 
			
		||||
- (void)didSelectType:(MPResultType)type;
 | 
			
		||||
- (MPResultType)selectedType;
 | 
			
		||||
 | 
			
		||||
@optional
 | 
			
		||||
- (MPSiteEntity *)selectedSite;
 | 
			
		||||
 
 | 
			
		||||
@@ -22,7 +22,7 @@
 | 
			
		||||
 | 
			
		||||
@interface MPTypeViewController()
 | 
			
		||||
 | 
			
		||||
- (MPSiteType)typeAtIndexPath:(NSIndexPath *)indexPath;
 | 
			
		||||
- (MPResultType)typeAtIndexPath:(NSIndexPath *)indexPath;
 | 
			
		||||
 | 
			
		||||
@end
 | 
			
		||||
 | 
			
		||||
@@ -75,11 +75,11 @@
 | 
			
		||||
    if ([self.delegate respondsToSelector:@selector( selectedSite )])
 | 
			
		||||
        selectedSite = [self.delegate selectedSite];
 | 
			
		||||
 | 
			
		||||
    MPSiteType cellType = [self typeAtIndexPath:indexPath];
 | 
			
		||||
    MPSiteType selectedType = selectedSite? selectedSite.type: [self.delegate selectedType];
 | 
			
		||||
    MPResultType cellType = [self typeAtIndexPath:indexPath];
 | 
			
		||||
    MPResultType selectedType = selectedSite? selectedSite.type: [self.delegate selectedType];
 | 
			
		||||
    cell.selected = (selectedType == cellType);
 | 
			
		||||
 | 
			
		||||
    if (cellType != (MPSiteType)NSNotFound && cellType & MPSiteTypeClassGenerated) {
 | 
			
		||||
    if (cellType != (MPResultType)NSNotFound && cellType & MPResultTypeClassTemplate) {
 | 
			
		||||
        [(UITextField *)[cell viewWithTag:2] setText:@"..."];
 | 
			
		||||
 | 
			
		||||
        NSString *name = selectedSite.name;
 | 
			
		||||
@@ -88,8 +88,8 @@
 | 
			
		||||
            counter = ((MPGeneratedSiteEntity *)selectedSite).counter;
 | 
			
		||||
 | 
			
		||||
        PearlNotMainQueue( ^{
 | 
			
		||||
            NSString *typeContent = [MPAlgorithmDefault generatePasswordForSiteNamed:name ofType:cellType
 | 
			
		||||
                                                                         withCounter:counter usingKey:[MPiOSAppDelegate get].key];
 | 
			
		||||
            NSString *typeContent = [MPAlgorithmDefault mpwTemplateForSiteNamed:name ofType:cellType
 | 
			
		||||
                                                                    withCounter:counter usingKey:[MPiOSAppDelegate get].key];
 | 
			
		||||
 | 
			
		||||
            PearlMainQueue( ^{
 | 
			
		||||
                [(UITextField *)[[tableView cellForRowAtIndexPath:indexPath] viewWithTag:2] setText:typeContent];
 | 
			
		||||
@@ -104,8 +104,8 @@
 | 
			
		||||
 | 
			
		||||
    NSAssert( self.navigationController.topViewController == self, @"Not the currently active navigation item." );
 | 
			
		||||
 | 
			
		||||
    MPSiteType type = [self typeAtIndexPath:indexPath];
 | 
			
		||||
    if (type == (MPSiteType)NSNotFound)
 | 
			
		||||
    MPResultType type = [self typeAtIndexPath:indexPath];
 | 
			
		||||
    if (type == (MPResultType)NSNotFound)
 | 
			
		||||
        // Selected a non-type row.
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
@@ -113,28 +113,28 @@
 | 
			
		||||
    [self.navigationController popViewControllerAnimated:YES];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (MPSiteType)typeAtIndexPath:(NSIndexPath *)indexPath {
 | 
			
		||||
- (MPResultType)typeAtIndexPath:(NSIndexPath *)indexPath {
 | 
			
		||||
 | 
			
		||||
    switch (indexPath.section) {
 | 
			
		||||
        case 0: {
 | 
			
		||||
            // Generated
 | 
			
		||||
            switch (indexPath.row) {
 | 
			
		||||
                case 0:
 | 
			
		||||
                    return (MPSiteType)NSNotFound;
 | 
			
		||||
                    return (MPResultType)NSNotFound;
 | 
			
		||||
                case 1:
 | 
			
		||||
                    return MPSiteTypeGeneratedMaximum;
 | 
			
		||||
                    return MPResultTypeTemplateMaximum;
 | 
			
		||||
                case 2:
 | 
			
		||||
                    return MPSiteTypeGeneratedLong;
 | 
			
		||||
                    return MPResultTypeTemplateLong;
 | 
			
		||||
                case 3:
 | 
			
		||||
                    return MPSiteTypeGeneratedMedium;
 | 
			
		||||
                    return MPResultTypeTemplateMedium;
 | 
			
		||||
                case 4:
 | 
			
		||||
                    return MPSiteTypeGeneratedBasic;
 | 
			
		||||
                    return MPResultTypeTemplateBasic;
 | 
			
		||||
                case 5:
 | 
			
		||||
                    return MPSiteTypeGeneratedShort;
 | 
			
		||||
                    return MPResultTypeTemplateShort;
 | 
			
		||||
                case 6:
 | 
			
		||||
                    return MPSiteTypeGeneratedPIN;
 | 
			
		||||
                    return MPResultTypeTemplatePIN;
 | 
			
		||||
                case 7:
 | 
			
		||||
                    return (MPSiteType)NSNotFound;
 | 
			
		||||
                    return (MPResultType)NSNotFound;
 | 
			
		||||
 | 
			
		||||
                default: {
 | 
			
		||||
                    Throw( @"Unsupported row: %ld, when selecting generated site type.", (long)indexPath.row );
 | 
			
		||||
@@ -146,13 +146,13 @@
 | 
			
		||||
            // Stored
 | 
			
		||||
            switch (indexPath.row) {
 | 
			
		||||
                case 0:
 | 
			
		||||
                    return (MPSiteType)NSNotFound;
 | 
			
		||||
                    return (MPResultType)NSNotFound;
 | 
			
		||||
                case 1:
 | 
			
		||||
                    return MPSiteTypeStoredPersonal;
 | 
			
		||||
                    return MPResultTypeStatefulPersonal;
 | 
			
		||||
                case 2:
 | 
			
		||||
                    return MPSiteTypeStoredDevicePrivate;
 | 
			
		||||
                    return MPResultTypeStatefulDevice;
 | 
			
		||||
                case 3:
 | 
			
		||||
                    return (MPSiteType)NSNotFound;
 | 
			
		||||
                    return (MPResultType)NSNotFound;
 | 
			
		||||
 | 
			
		||||
                default: {
 | 
			
		||||
                    Throw( @"Unsupported row: %ld, when selecting stored site type.", (long)indexPath.row );
 | 
			
		||||
 
 | 
			
		||||
@@ -20,6 +20,7 @@
 | 
			
		||||
#import "MPAppDelegate_Key.h"
 | 
			
		||||
#import "MPAppDelegate_Store.h"
 | 
			
		||||
#import "MPStoreViewController.h"
 | 
			
		||||
#import "mpw-marshall.h"
 | 
			
		||||
 | 
			
		||||
@interface MPiOSAppDelegate()<UIDocumentInteractionControllerDelegate>
 | 
			
		||||
 | 
			
		||||
@@ -177,62 +178,46 @@
 | 
			
		||||
    return YES;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (void)importSites:(NSString *)importedSitesString {
 | 
			
		||||
- (void)importSites:(NSString *)importData {
 | 
			
		||||
 | 
			
		||||
    if ([NSThread isMainThread]) {
 | 
			
		||||
        PearlNotMainQueue( ^{
 | 
			
		||||
            [self importSites:importedSitesString];
 | 
			
		||||
            [self importSites:importData];
 | 
			
		||||
        } );
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    PearlOverlay *activityOverlay = [PearlOverlay showProgressOverlayWithTitle:@"Importing"];
 | 
			
		||||
    MPImportResult result = [self importSites:importedSitesString askImportPassword:^NSString *(NSString *userName) {
 | 
			
		||||
    [self importSites:importData askImportPassword:^NSString *(NSString *userName) {
 | 
			
		||||
        return PearlAwait( ^(void (^setResult)(id)) {
 | 
			
		||||
            [PearlAlert showAlertWithTitle:@"Import File's Master Password"
 | 
			
		||||
                                   message:strf( @"%@'s export was done using a different master password.\n"
 | 
			
		||||
                                           @"Enter that master password to unlock the exported data.", userName )
 | 
			
		||||
            [PearlAlert showAlertWithTitle:strf( @"Importing Sites For\n%@", userName )
 | 
			
		||||
                                   message:@"Enter the master password used to create this export file."
 | 
			
		||||
                                 viewStyle:UIAlertViewStyleSecureTextInput
 | 
			
		||||
                                 initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
 | 
			
		||||
                        if (buttonIndex_ == [alert_ cancelButtonIndex])
 | 
			
		||||
                            setResult( nil );
 | 
			
		||||
                        else
 | 
			
		||||
                            setResult( [alert_ textFieldAtIndex:0].text );
 | 
			
		||||
                    }
 | 
			
		||||
                               cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Unlock Import", nil];
 | 
			
		||||
                    }          cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Import", nil];
 | 
			
		||||
        } );
 | 
			
		||||
    }                         askUserPassword:^NSString *(NSString *userName, NSUInteger importCount, NSUInteger deleteCount) {
 | 
			
		||||
    } askUserPassword:^NSString *(NSString *userName) {
 | 
			
		||||
        return PearlAwait( (id)^(void (^setResult)(id)) {
 | 
			
		||||
            [PearlAlert showAlertWithTitle:strf( @"Master Password for\n%@", userName )
 | 
			
		||||
                                   message:strf( @"Imports %lu sites, overwriting %lu.",
 | 
			
		||||
                                           (unsigned long)importCount, (unsigned long)deleteCount )
 | 
			
		||||
            [PearlAlert showAlertWithTitle:strf( @"Master Password For\n%@", userName )
 | 
			
		||||
                                   message:@"Enter the current master password for this user."
 | 
			
		||||
                                 viewStyle:UIAlertViewStyleSecureTextInput
 | 
			
		||||
                                 initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
 | 
			
		||||
                        if (buttonIndex_ == [alert_ cancelButtonIndex])
 | 
			
		||||
                            setResult( nil );
 | 
			
		||||
                        else
 | 
			
		||||
                            setResult( [alert_ textFieldAtIndex:0].text );
 | 
			
		||||
                    }
 | 
			
		||||
                               cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Import", nil];
 | 
			
		||||
                    }          cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Import", nil];
 | 
			
		||||
        } );
 | 
			
		||||
    }          result:^(NSError *error) {
 | 
			
		||||
        [activityOverlay cancelOverlayAnimated:YES];
 | 
			
		||||
 | 
			
		||||
        if (error && !(error.domain == NSCocoaErrorDomain && error.code == NSUserCancelledError))
 | 
			
		||||
            [PearlAlert showError:error.localizedDescription];
 | 
			
		||||
    }];
 | 
			
		||||
 | 
			
		||||
    switch (result) {
 | 
			
		||||
        case MPImportResultSuccess:
 | 
			
		||||
        case MPImportResultCancelled:
 | 
			
		||||
            break;
 | 
			
		||||
        case MPImportResultInternalError:
 | 
			
		||||
            [PearlAlert showError:@"Import failed because of an internal error."];
 | 
			
		||||
            break;
 | 
			
		||||
        case MPImportResultMalformedInput:
 | 
			
		||||
            [PearlAlert showError:@"The import doesn't look like a Master Password export."];
 | 
			
		||||
            break;
 | 
			
		||||
        case MPImportResultInvalidPassword:
 | 
			
		||||
            [PearlAlert showError:@"Incorrect master password for the import sites."];
 | 
			
		||||
            break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [activityOverlay cancelOverlayAnimated:YES];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (void)applicationWillEnterForeground:(UIApplication *)application {
 | 
			
		||||
@@ -250,10 +235,9 @@
 | 
			
		||||
    [[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification object:nil];
 | 
			
		||||
 | 
			
		||||
    PearlNotMainQueue( ^{
 | 
			
		||||
        NSString *importHeader = @"# Master Password site export";
 | 
			
		||||
        NSString *importedSitesString = [UIPasteboard generalPasteboard].string;
 | 
			
		||||
        if ([importedSitesString length] > [importHeader length] &&
 | 
			
		||||
            [[importedSitesString substringToIndex:[importHeader length]] isEqualToString:importHeader])
 | 
			
		||||
        NSString *importData = [UIPasteboard generalPasteboard].string;
 | 
			
		||||
        MPMarshallInfo *importInfo = mpw_marshall_read_info( importData.UTF8String );
 | 
			
		||||
        if (importInfo->format != MPMarshallFormatNone)
 | 
			
		||||
            [PearlAlert showAlertWithTitle:@"Import Sites?" message:
 | 
			
		||||
                            @"We've detected Master Password import sites on your pasteboard, would you like to import them?"
 | 
			
		||||
                                 viewStyle:UIAlertViewStyleDefault initAlert:nil
 | 
			
		||||
@@ -261,9 +245,10 @@
 | 
			
		||||
                             if (buttonIndex == [alert cancelButtonIndex])
 | 
			
		||||
                                 return;
 | 
			
		||||
 | 
			
		||||
                             [self importSites:importedSitesString];
 | 
			
		||||
                             [self importSites:importData];
 | 
			
		||||
                             [UIPasteboard generalPasteboard].string = @"";
 | 
			
		||||
                         } cancelTitle:@"No" otherTitles:@"Import Sites", nil];
 | 
			
		||||
        mpw_marshal_info_free( importInfo );
 | 
			
		||||
    } );
 | 
			
		||||
 | 
			
		||||
    [super applicationDidBecomeActive:application];
 | 
			
		||||
@@ -449,62 +434,86 @@
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    NSString *exportedSites = [self exportSitesRevealPasswords:revealPasswords];
 | 
			
		||||
    NSString *message;
 | 
			
		||||
    [self exportSitesRevealPasswords:revealPasswords askExportPassword:^NSString *(NSString *userName) {
 | 
			
		||||
        return PearlAwait( ^(void (^setResult)(id)) {
 | 
			
		||||
            [PearlAlert showAlertWithTitle:@"Import File's Master Password"
 | 
			
		||||
                                   message:strf( @"%@'s export was done using a different master password.\n"
 | 
			
		||||
                                           @"Enter that master password to unlock the exported data.", userName )
 | 
			
		||||
                                 viewStyle:UIAlertViewStyleSecureTextInput
 | 
			
		||||
                                 initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
 | 
			
		||||
                        if (buttonIndex_ == [alert_ cancelButtonIndex])
 | 
			
		||||
                            setResult( nil );
 | 
			
		||||
                        else
 | 
			
		||||
                            setResult( [alert_ textFieldAtIndex:0].text );
 | 
			
		||||
                    }
 | 
			
		||||
                               cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Unlock Import", nil];
 | 
			
		||||
        } );
 | 
			
		||||
    }                         result:^(NSString *mpsites, NSError *error) {
 | 
			
		||||
        if (!mpsites || error) {
 | 
			
		||||
            MPError( error, @"Failed to export mpsites." );
 | 
			
		||||
            [PearlAlert showAlertWithTitle:@"Export Error"
 | 
			
		||||
                                   message:error.localizedDescription
 | 
			
		||||
                                 viewStyle:UIAlertViewStyleDefault
 | 
			
		||||
                                 initAlert:nil tappedButtonBlock:nil cancelTitle:[PearlStrings get].commonButtonOkay
 | 
			
		||||
                               otherTitles:nil];
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    if (revealPasswords)
 | 
			
		||||
        message = strf( @"Export of Master Password sites with passwords included.\n\n"
 | 
			
		||||
                        @"REMINDER: Make sure nobody else sees this file!  Passwords are visible!\n\n\n"
 | 
			
		||||
                        @"--\n"
 | 
			
		||||
                        @"%@\n"
 | 
			
		||||
                        @"Master Password %@, build %@",
 | 
			
		||||
                [self activeUserForMainThread].name,
 | 
			
		||||
                [PearlInfoPlist get].CFBundleShortVersionString,
 | 
			
		||||
                [PearlInfoPlist get].CFBundleVersion );
 | 
			
		||||
    else
 | 
			
		||||
        message = strf( @"Backup of Master Password sites.\n\n\n"
 | 
			
		||||
                        @"--\n"
 | 
			
		||||
                        @"%@\n"
 | 
			
		||||
                        @"Master Password %@, build %@",
 | 
			
		||||
                [self activeUserForMainThread].name,
 | 
			
		||||
                [PearlInfoPlist get].CFBundleShortVersionString,
 | 
			
		||||
                [PearlInfoPlist get].CFBundleVersion );
 | 
			
		||||
        [PearlSheet showSheetWithTitle:@"Export Destination" viewStyle:UIActionSheetStyleBlackTranslucent initSheet:nil
 | 
			
		||||
                     tappedButtonBlock:^(UIActionSheet *sheet, NSInteger buttonIndex) {
 | 
			
		||||
                         if (buttonIndex == [sheet cancelButtonIndex])
 | 
			
		||||
                             return;
 | 
			
		||||
 | 
			
		||||
    NSDateFormatter *exportDateFormatter = [NSDateFormatter new];
 | 
			
		||||
    [exportDateFormatter setDateFormat:@"yyyy'-'MM'-'dd"];
 | 
			
		||||
                         NSDateFormatter *exportDateFormatter = [NSDateFormatter new];
 | 
			
		||||
                         [exportDateFormatter setDateFormat:@"yyyy'-'MM'-'dd"];
 | 
			
		||||
                         NSString *exportFileName = strf( @"%@ (%@).mpsites",
 | 
			
		||||
                                 [self activeUserForMainThread].name, [exportDateFormatter stringFromDate:[NSDate date]] );
 | 
			
		||||
 | 
			
		||||
    NSString *exportFileName = strf( @"%@ (%@).mpsites",
 | 
			
		||||
            [self activeUserForMainThread].name, [exportDateFormatter stringFromDate:[NSDate date]] );
 | 
			
		||||
    [PearlSheet showSheetWithTitle:@"Export Destination" viewStyle:UIActionSheetStyleBlackTranslucent initSheet:nil
 | 
			
		||||
                 tappedButtonBlock:^(UIActionSheet *sheet, NSInteger buttonIndex) {
 | 
			
		||||
                     if (buttonIndex == [sheet cancelButtonIndex])
 | 
			
		||||
                         return;
 | 
			
		||||
                         if (buttonIndex == [sheet firstOtherButtonIndex]) {
 | 
			
		||||
                             NSString *message;
 | 
			
		||||
                             if (revealPasswords)
 | 
			
		||||
                                 message = strf( @"Export of Master Password sites with passwords included.\n\n"
 | 
			
		||||
                                                 @"REMINDER: Make sure nobody else sees this file!  Passwords are visible!\n\n\n"
 | 
			
		||||
                                                 @"--\n"
 | 
			
		||||
                                                 @"%@\n"
 | 
			
		||||
                                                 @"Master Password %@, build %@",
 | 
			
		||||
                                         [self activeUserForMainThread].name,
 | 
			
		||||
                                         [PearlInfoPlist get].CFBundleShortVersionString,
 | 
			
		||||
                                         [PearlInfoPlist get].CFBundleVersion );
 | 
			
		||||
                             else
 | 
			
		||||
                                 message = strf( @"Backup of Master Password sites.\n\n\n"
 | 
			
		||||
                                                 @"--\n"
 | 
			
		||||
                                                 @"%@\n"
 | 
			
		||||
                                                 @"Master Password %@, build %@",
 | 
			
		||||
                                         [self activeUserForMainThread].name,
 | 
			
		||||
                                         [PearlInfoPlist get].CFBundleShortVersionString,
 | 
			
		||||
                                         [PearlInfoPlist get].CFBundleVersion );
 | 
			
		||||
 | 
			
		||||
                     if (buttonIndex == [sheet firstOtherButtonIndex]) {
 | 
			
		||||
                         [PearlEMail sendEMailTo:nil fromVC:viewController subject:@"Master Password Export" body:message
 | 
			
		||||
                                     attachments:[[PearlEMailAttachment alloc]
 | 
			
		||||
                                                         initWithContent:[exportedSites dataUsingEncoding:NSUTF8StringEncoding]
 | 
			
		||||
                                                                mimeType:@"text/plain" fileName:exportFileName],
 | 
			
		||||
                                                 nil];
 | 
			
		||||
                         return;
 | 
			
		||||
                     }
 | 
			
		||||
                             [PearlEMail sendEMailTo:nil fromVC:viewController subject:@"Master Password Export" body:message
 | 
			
		||||
                                         attachments:[[PearlEMailAttachment alloc]
 | 
			
		||||
                                                             initWithContent:[mpsites dataUsingEncoding:NSUTF8StringEncoding]
 | 
			
		||||
                                                                    mimeType:@"text/plain" fileName:exportFileName],
 | 
			
		||||
                                                     nil];
 | 
			
		||||
                             return;
 | 
			
		||||
                         }
 | 
			
		||||
 | 
			
		||||
                     NSURL *applicationSupportURL = [[[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory
 | 
			
		||||
                                                                                            inDomains:NSUserDomainMask] lastObject];
 | 
			
		||||
                     NSURL *exportURL = [[applicationSupportURL
 | 
			
		||||
                             URLByAppendingPathComponent:[NSBundle mainBundle].bundleIdentifier isDirectory:YES]
 | 
			
		||||
                             URLByAppendingPathComponent:exportFileName isDirectory:NO];
 | 
			
		||||
                     NSError *error = nil;
 | 
			
		||||
                     if (![[exportedSites dataUsingEncoding:NSUTF8StringEncoding]
 | 
			
		||||
                             writeToURL:exportURL options:NSDataWritingFileProtectionComplete error:&error])
 | 
			
		||||
                         MPError( error, @"Failed to write export data to URL %@.", exportURL );
 | 
			
		||||
                     else {
 | 
			
		||||
                         self.interactionController = [UIDocumentInteractionController interactionControllerWithURL:exportURL];
 | 
			
		||||
                         self.interactionController.UTI = @"com.lyndir.masterpassword.sites";
 | 
			
		||||
                         self.interactionController.delegate = self;
 | 
			
		||||
                         [self.interactionController presentOpenInMenuFromRect:CGRectZero inView:viewController.view animated:YES];
 | 
			
		||||
                     }
 | 
			
		||||
                 } cancelTitle:@"Cancel" destructiveTitle:nil otherTitles:@"Send As E-Mail", @"Share / Airdrop", nil];
 | 
			
		||||
                         NSURL *applicationSupportURL = [[[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory
 | 
			
		||||
                                                                                                inDomains:NSUserDomainMask] lastObject];
 | 
			
		||||
                         NSURL *exportURL = [[applicationSupportURL
 | 
			
		||||
                                 URLByAppendingPathComponent:[NSBundle mainBundle].bundleIdentifier isDirectory:YES]
 | 
			
		||||
                                 URLByAppendingPathComponent:exportFileName isDirectory:NO];
 | 
			
		||||
                         NSError *writeError = nil;
 | 
			
		||||
                         if (![[mpsites dataUsingEncoding:NSUTF8StringEncoding]
 | 
			
		||||
                                 writeToURL:exportURL options:NSDataWritingFileProtectionComplete error:&writeError])
 | 
			
		||||
                             MPError( writeError, @"Failed to write export data to URL %@.", exportURL );
 | 
			
		||||
                         else {
 | 
			
		||||
                             self.interactionController = [UIDocumentInteractionController interactionControllerWithURL:exportURL];
 | 
			
		||||
                             self.interactionController.UTI = @"com.lyndir.masterpassword.sites";
 | 
			
		||||
                             self.interactionController.delegate = self;
 | 
			
		||||
                             [self.interactionController presentOpenInMenuFromRect:CGRectZero inView:viewController.view animated:YES];
 | 
			
		||||
                         }
 | 
			
		||||
                     } cancelTitle:@"Cancel" destructiveTitle:nil otherTitles:@"Send As E-Mail", @"Share / Airdrop", nil];
 | 
			
		||||
    }];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (void)changeMasterPasswordFor:(MPUserEntity *)user saveInContext:(NSManagedObjectContext *)moc didResetBlock:(void ( ^ )(void))didReset {
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,7 @@
 | 
			
		||||
#endif
 | 
			
		||||
#define MP_ENV_fullName     "MP_FULLNAME"
 | 
			
		||||
#define MP_ENV_algorithm    "MP_ALGORITHM"
 | 
			
		||||
#define MP_ENV_format       "MP_FORMAT"
 | 
			
		||||
 | 
			
		||||
static void usage() {
 | 
			
		||||
 | 
			
		||||
@@ -75,10 +76,11 @@ static void usage() {
 | 
			
		||||
            "  -f|F format  The mpsites format to use for reading/writing site parameters.\n"
 | 
			
		||||
            "               -F forces the use of the given format,\n"
 | 
			
		||||
            "               -f allows fallback/migration.\n"
 | 
			
		||||
            "               Defaults to json, falls back to plain.\n"
 | 
			
		||||
            "               Defaults to %s in env or json, falls back to plain.\n"
 | 
			
		||||
            "                   n, none     | No file\n"
 | 
			
		||||
            "                   f, flat     | ~/.mpw.d/Full Name.%s\n"
 | 
			
		||||
            "                   j, json     | ~/.mpw.d/Full Name.%s\n\n",
 | 
			
		||||
            mpw_marshall_format_extension( MPMarshallFormatFlat ), mpw_marshall_format_extension( MPMarshallFormatJSON ) );
 | 
			
		||||
            MP_ENV_format, mpw_marshall_format_extension( MPMarshallFormatFlat ), mpw_marshall_format_extension( MPMarshallFormatJSON ) );
 | 
			
		||||
    inf( ""
 | 
			
		||||
            "  -R redacted  Whether to save the mpsites in redacted format or not.\n"
 | 
			
		||||
            "               Defaults to 1, redacted.\n\n" );
 | 
			
		||||
@@ -166,6 +168,7 @@ int main(int argc, char *const argv[]) {
 | 
			
		||||
    const char *keyPurposeArg = NULL, *keyContextArg = NULL, *sitesFormatArg = NULL, *sitesRedactedArg = NULL;
 | 
			
		||||
    fullNameArg = mpw_getenv( MP_ENV_fullName );
 | 
			
		||||
    algorithmVersionArg = mpw_getenv( MP_ENV_algorithm );
 | 
			
		||||
    sitesFormatArg = mpw_getenv( MP_ENV_format );
 | 
			
		||||
 | 
			
		||||
    // Read the command-line options.
 | 
			
		||||
    for (int opt; (opt = getopt( argc, argv, "u:U:M:t:P:c:a:s:p:C:f:F:R:vqh" )) != EOF;)
 | 
			
		||||
@@ -269,13 +272,14 @@ int main(int argc, char *const argv[]) {
 | 
			
		||||
    char *sitesPath = mpw_path( fullName, mpw_marshall_format_extension( sitesFormat ) );
 | 
			
		||||
    if (!sitesPath || !(sitesFile = fopen( sitesPath, "r" ))) {
 | 
			
		||||
        dbg( "Couldn't open configuration file:\n  %s: %s\n", sitesPath, strerror( errno ) );
 | 
			
		||||
        free( sitesPath );
 | 
			
		||||
 | 
			
		||||
        // Try to fall back to the flat format.
 | 
			
		||||
        if (!sitesFormatFixed) {
 | 
			
		||||
            sitesFormat = MPMarshallFormatFlat;
 | 
			
		||||
            sitesPath = mpw_path( fullName, mpw_marshall_format_extension( sitesFormat ) );
 | 
			
		||||
            if (!sitesPath || !(sitesFile = fopen( sitesPath, "r" )))
 | 
			
		||||
            free( sitesPath );
 | 
			
		||||
            sitesPath = mpw_path( fullName, mpw_marshall_format_extension( MPMarshallFormatFlat ) );
 | 
			
		||||
            if (sitesPath && (sitesFile = fopen( sitesPath, "r" )))
 | 
			
		||||
                sitesFormat = MPMarshallFormatFlat;
 | 
			
		||||
            else
 | 
			
		||||
                dbg( "Couldn't open configuration file:\n  %s: %s\n", sitesPath, strerror( errno ) );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -290,9 +294,9 @@ int main(int argc, char *const argv[]) {
 | 
			
		||||
    else {
 | 
			
		||||
        // Read file.
 | 
			
		||||
        size_t readAmount = 4096, bufSize = 0, bufOffset = 0, readSize = 0;
 | 
			
		||||
        char *buf = NULL;
 | 
			
		||||
        while ((mpw_realloc( &buf, &bufSize, readAmount )) &&
 | 
			
		||||
               (bufOffset += (readSize = fread( buf + bufOffset, 1, readAmount, sitesFile ))) &&
 | 
			
		||||
        char *sitesInputData = NULL;
 | 
			
		||||
        while ((mpw_realloc( &sitesInputData, &bufSize, readAmount )) &&
 | 
			
		||||
               (bufOffset += (readSize = fread( sitesInputData + bufOffset, 1, readAmount, sitesFile ))) &&
 | 
			
		||||
               (readSize == readAmount));
 | 
			
		||||
        if (ferror( sitesFile ))
 | 
			
		||||
            wrn( "Error while reading configuration file:\n  %s: %d\n", sitesPath, ferror( sitesFile ) );
 | 
			
		||||
@@ -300,13 +304,14 @@ int main(int argc, char *const argv[]) {
 | 
			
		||||
 | 
			
		||||
        // Parse file.
 | 
			
		||||
        MPMarshallError marshallError = { .type = MPMarshallSuccess };
 | 
			
		||||
        user = mpw_marshall_read( buf, sitesFormat, masterPassword, &marshallError );
 | 
			
		||||
        MPMarshallFormat sitesInputFormat = sitesFormatArg? sitesFormat: mpw_marshall_format_guess( sitesInputData );
 | 
			
		||||
        user = mpw_marshall_read( sitesInputData, sitesInputFormat, masterPassword, &marshallError );
 | 
			
		||||
        if (marshallError.type == MPMarshallErrorMasterPassword) {
 | 
			
		||||
            // Incorrect master password.
 | 
			
		||||
            if (!allowPasswordUpdate) {
 | 
			
		||||
                ftl( "Incorrect master password according to configuration:\n  %s: %s\n", sitesPath, marshallError.description );
 | 
			
		||||
                mpw_marshal_free( user );
 | 
			
		||||
                mpw_free( buf, bufSize );
 | 
			
		||||
                mpw_free( sitesInputData, bufSize );
 | 
			
		||||
                free( sitesPath );
 | 
			
		||||
                return EX_DATAERR;
 | 
			
		||||
            }
 | 
			
		||||
@@ -321,14 +326,14 @@ int main(int argc, char *const argv[]) {
 | 
			
		||||
                    importMasterPassword = mpw_getpass( "Old master password: " );
 | 
			
		||||
 | 
			
		||||
                mpw_marshal_free( user );
 | 
			
		||||
                user = mpw_marshall_read( buf, sitesFormat, importMasterPassword, &marshallError );
 | 
			
		||||
                user = mpw_marshall_read( sitesInputData, sitesInputFormat, importMasterPassword, &marshallError );
 | 
			
		||||
            }
 | 
			
		||||
            if (user) {
 | 
			
		||||
                mpw_free_string( user->masterPassword );
 | 
			
		||||
                user->masterPassword = strdup( masterPassword );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        mpw_free( buf, bufSize );
 | 
			
		||||
        mpw_free( sitesInputData, bufSize );
 | 
			
		||||
        if (!user || marshallError.type != MPMarshallSuccess) {
 | 
			
		||||
            err( "Couldn't parse configuration file:\n  %s: %s\n", sitesPath, marshallError.description );
 | 
			
		||||
            mpw_marshal_free( user );
 | 
			
		||||
@@ -471,7 +476,7 @@ int main(int argc, char *const argv[]) {
 | 
			
		||||
    if (keyPurpose == MPKeyPurposeIdentification && site && !site->loginGenerated && site->loginName)
 | 
			
		||||
        fprintf( stdout, "%s\n", site->loginName );
 | 
			
		||||
 | 
			
		||||
    else if (resultParam && site && resultType & MPResultTypeClassState) {
 | 
			
		||||
    else if (resultParam && site && resultType & MPResultTypeClassStateful) {
 | 
			
		||||
        mpw_free_string( site->content );
 | 
			
		||||
        if (!(site->content = mpw_siteState( masterKey, siteName, siteCounter,
 | 
			
		||||
                keyPurpose, keyContext, resultType, resultParam, algorithmVersion ))) {
 | 
			
		||||
@@ -483,7 +488,7 @@ int main(int argc, char *const argv[]) {
 | 
			
		||||
        inf( "saved.\n" );
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        if (!resultParam && site && site->content && resultType & MPResultTypeClassState)
 | 
			
		||||
        if (!resultParam && site && site->content && resultType & MPResultTypeClassStateful)
 | 
			
		||||
            resultParam = strdup( site->content );
 | 
			
		||||
        const char *siteResult = mpw_siteResult( masterKey, siteName, siteCounter,
 | 
			
		||||
                keyPurpose, keyContext, resultType, resultParam, algorithmVersion );
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user