Add WIP GeminiProtocol framework

This commit is contained in:
Shadowfacts 2020-07-12 23:09:37 -04:00
parent a84a223c68
commit 4db28d377a
Signed by: shadowfacts
GPG Key ID: 94A5AB95422746E5
13 changed files with 863 additions and 0 deletions

View File

@ -12,6 +12,18 @@
D626646324BBF1C300DF9B88 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D626646224BBF1C300DF9B88 /* Assets.xcassets */; }; D626646324BBF1C300DF9B88 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D626646224BBF1C300DF9B88 /* Assets.xcassets */; };
D626646624BBF1C300DF9B88 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D626646524BBF1C300DF9B88 /* Preview Assets.xcassets */; }; D626646624BBF1C300DF9B88 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D626646524BBF1C300DF9B88 /* Preview Assets.xcassets */; };
D626646924BBF1C300DF9B88 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D626646724BBF1C300DF9B88 /* Main.storyboard */; }; D626646924BBF1C300DF9B88 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D626646724BBF1C300DF9B88 /* Main.storyboard */; };
D626648024BBF22E00DF9B88 /* GeminiProtocol.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D626647724BBF22E00DF9B88 /* GeminiProtocol.framework */; };
D626648724BBF22E00DF9B88 /* GeminiProtocolTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626648624BBF22E00DF9B88 /* GeminiProtocolTests.swift */; };
D626648924BBF22E00DF9B88 /* GeminiProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D626647924BBF22E00DF9B88 /* GeminiProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; };
D626648C24BBF22E00DF9B88 /* GeminiProtocol.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D626647724BBF22E00DF9B88 /* GeminiProtocol.framework */; };
D626648D24BBF22E00DF9B88 /* GeminiProtocol.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D626647724BBF22E00DF9B88 /* GeminiProtocol.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
D626649C24BBF24100DF9B88 /* GeminiResponseHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626649524BBF24000DF9B88 /* GeminiResponseHeader.swift */; };
D626649D24BBF24100DF9B88 /* Message+Gemini.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626649624BBF24100DF9B88 /* Message+Gemini.swift */; };
D626649E24BBF24100DF9B88 /* GeminiConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626649724BBF24100DF9B88 /* GeminiConnection.swift */; };
D626649F24BBF24100DF9B88 /* NWParameters+Gemini.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626649824BBF24100DF9B88 /* NWParameters+Gemini.swift */; };
D62664A024BBF24100DF9B88 /* GeminiProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626649924BBF24100DF9B88 /* GeminiProtocol.swift */; };
D62664A124BBF24100DF9B88 /* GeminiRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626649A24BBF24100DF9B88 /* GeminiRequest.swift */; };
D62664A224BBF24100DF9B88 /* GeminiResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626649B24BBF24100DF9B88 /* GeminiResponse.swift */; };
D62664B124BBF26A00DF9B88 /* GeminiFormat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D62664A824BBF26A00DF9B88 /* GeminiFormat.framework */; }; D62664B124BBF26A00DF9B88 /* GeminiFormat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D62664A824BBF26A00DF9B88 /* GeminiFormat.framework */; };
D62664B824BBF26A00DF9B88 /* GeminiFormatTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62664B724BBF26A00DF9B88 /* GeminiFormatTests.swift */; }; D62664B824BBF26A00DF9B88 /* GeminiFormatTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62664B724BBF26A00DF9B88 /* GeminiFormatTests.swift */; };
D62664BA24BBF26A00DF9B88 /* GeminiFormat.h in Headers */ = {isa = PBXBuildFile; fileRef = D62664AA24BBF26A00DF9B88 /* GeminiFormat.h */; settings = {ATTRIBUTES = (Public, ); }; }; D62664BA24BBF26A00DF9B88 /* GeminiFormat.h in Headers */ = {isa = PBXBuildFile; fileRef = D62664AA24BBF26A00DF9B88 /* GeminiFormat.h */; settings = {ATTRIBUTES = (Public, ); }; };
@ -21,6 +33,14 @@
D62664C824BBF2C600DF9B88 /* Document.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62664C724BBF2C600DF9B88 /* Document.swift */; }; D62664C824BBF2C600DF9B88 /* Document.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62664C724BBF2C600DF9B88 /* Document.swift */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
D626648124BBF22E00DF9B88 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = D626645324BBF1C200DF9B88 /* Project object */;
proxyType = 1;
remoteGlobalIDString = D626647624BBF22E00DF9B88;
remoteInfo = GeminiProtocol;
};
D626648324BBF22E00DF9B88 /* PBXContainerItemProxy */ = { D626648324BBF22E00DF9B88 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy; isa = PBXContainerItemProxy;
containerPortal = D626645324BBF1C200DF9B88 /* Project object */; containerPortal = D626645324BBF1C200DF9B88 /* Project object */;
@ -28,6 +48,13 @@
remoteGlobalIDString = D626645A24BBF1C200DF9B88; remoteGlobalIDString = D626645A24BBF1C200DF9B88;
remoteInfo = Gemini; remoteInfo = Gemini;
}; };
D626648A24BBF22E00DF9B88 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = D626645324BBF1C200DF9B88 /* Project object */;
proxyType = 1;
remoteGlobalIDString = D626647624BBF22E00DF9B88;
remoteInfo = GeminiProtocol;
};
D62664B224BBF26A00DF9B88 /* PBXContainerItemProxy */ = { D62664B224BBF26A00DF9B88 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy; isa = PBXContainerItemProxy;
containerPortal = D626645324BBF1C200DF9B88 /* Project object */; containerPortal = D626645324BBF1C200DF9B88 /* Project object */;
@ -59,6 +86,7 @@
dstSubfolderSpec = 10; dstSubfolderSpec = 10;
files = ( files = (
D62664BE24BBF26A00DF9B88 /* GeminiFormat.framework in Embed Frameworks */, D62664BE24BBF26A00DF9B88 /* GeminiFormat.framework in Embed Frameworks */,
D626648D24BBF22E00DF9B88 /* GeminiProtocol.framework in Embed Frameworks */,
); );
name = "Embed Frameworks"; name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@ -74,6 +102,19 @@
D626646824BBF1C300DF9B88 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; }; D626646824BBF1C300DF9B88 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
D626646A24BBF1C300DF9B88 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; D626646A24BBF1C300DF9B88 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
D626646B24BBF1C300DF9B88 /* Gemini.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Gemini.entitlements; sourceTree = "<group>"; }; D626646B24BBF1C300DF9B88 /* Gemini.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Gemini.entitlements; sourceTree = "<group>"; };
D626647724BBF22E00DF9B88 /* GeminiProtocol.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = GeminiProtocol.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D626647924BBF22E00DF9B88 /* GeminiProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeminiProtocol.h; sourceTree = "<group>"; };
D626647A24BBF22E00DF9B88 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
D626647F24BBF22E00DF9B88 /* GeminiProtocolTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = GeminiProtocolTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
D626648624BBF22E00DF9B88 /* GeminiProtocolTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeminiProtocolTests.swift; sourceTree = "<group>"; };
D626648824BBF22E00DF9B88 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
D626649524BBF24000DF9B88 /* GeminiResponseHeader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeminiResponseHeader.swift; sourceTree = "<group>"; };
D626649624BBF24100DF9B88 /* Message+Gemini.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Message+Gemini.swift"; sourceTree = "<group>"; };
D626649724BBF24100DF9B88 /* GeminiConnection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeminiConnection.swift; sourceTree = "<group>"; };
D626649824BBF24100DF9B88 /* NWParameters+Gemini.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NWParameters+Gemini.swift"; sourceTree = "<group>"; };
D626649924BBF24100DF9B88 /* GeminiProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeminiProtocol.swift; sourceTree = "<group>"; };
D626649A24BBF24100DF9B88 /* GeminiRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeminiRequest.swift; sourceTree = "<group>"; };
D626649B24BBF24100DF9B88 /* GeminiResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeminiResponse.swift; sourceTree = "<group>"; };
D62664A824BBF26A00DF9B88 /* GeminiFormat.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = GeminiFormat.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D62664A824BBF26A00DF9B88 /* GeminiFormat.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = GeminiFormat.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D62664AA24BBF26A00DF9B88 /* GeminiFormat.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeminiFormat.h; sourceTree = "<group>"; }; D62664AA24BBF26A00DF9B88 /* GeminiFormat.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeminiFormat.h; sourceTree = "<group>"; };
D62664AB24BBF26A00DF9B88 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; D62664AB24BBF26A00DF9B88 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@ -90,6 +131,25 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
D62664BD24BBF26A00DF9B88 /* GeminiFormat.framework in Frameworks */, D62664BD24BBF26A00DF9B88 /* GeminiFormat.framework in Frameworks */,
D626648C24BBF22E00DF9B88 /* GeminiProtocol.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
D626647424BBF22E00DF9B88 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
D626647C24BBF22E00DF9B88 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
D626648024BBF22E00DF9B88 /* GeminiProtocol.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
D62664A524BBF26A00DF9B88 /* Frameworks */ = { D62664A524BBF26A00DF9B88 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
@ -112,6 +172,8 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
D626645D24BBF1C200DF9B88 /* Gemini */, D626645D24BBF1C200DF9B88 /* Gemini */,
D626647824BBF22E00DF9B88 /* GeminiProtocol */,
D626648524BBF22E00DF9B88 /* GeminiProtocolTests */,
D62664A924BBF26A00DF9B88 /* GeminiFormat */, D62664A924BBF26A00DF9B88 /* GeminiFormat */,
D62664B624BBF26A00DF9B88 /* GeminiFormatTests */, D62664B624BBF26A00DF9B88 /* GeminiFormatTests */,
D626645C24BBF1C200DF9B88 /* Products */, D626645C24BBF1C200DF9B88 /* Products */,
@ -122,6 +184,8 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
D626645B24BBF1C200DF9B88 /* Gemini.app */, D626645B24BBF1C200DF9B88 /* Gemini.app */,
D626647724BBF22E00DF9B88 /* GeminiProtocol.framework */,
D626647F24BBF22E00DF9B88 /* GeminiProtocolTests.xctest */,
D62664A824BBF26A00DF9B88 /* GeminiFormat.framework */, D62664A824BBF26A00DF9B88 /* GeminiFormat.framework */,
D62664B024BBF26A00DF9B88 /* GeminiFormatTests.xctest */, D62664B024BBF26A00DF9B88 /* GeminiFormatTests.xctest */,
); );
@ -150,6 +214,31 @@
path = "Preview Content"; path = "Preview Content";
sourceTree = "<group>"; sourceTree = "<group>";
}; };
D626647824BBF22E00DF9B88 /* GeminiProtocol */ = {
isa = PBXGroup;
children = (
D626647924BBF22E00DF9B88 /* GeminiProtocol.h */,
D626647A24BBF22E00DF9B88 /* Info.plist */,
D626649824BBF24100DF9B88 /* NWParameters+Gemini.swift */,
D626649624BBF24100DF9B88 /* Message+Gemini.swift */,
D626649924BBF24100DF9B88 /* GeminiProtocol.swift */,
D626649A24BBF24100DF9B88 /* GeminiRequest.swift */,
D626649B24BBF24100DF9B88 /* GeminiResponse.swift */,
D626649524BBF24000DF9B88 /* GeminiResponseHeader.swift */,
D626649724BBF24100DF9B88 /* GeminiConnection.swift */,
);
path = GeminiProtocol;
sourceTree = "<group>";
};
D626648524BBF22E00DF9B88 /* GeminiProtocolTests */ = {
isa = PBXGroup;
children = (
D626648624BBF22E00DF9B88 /* GeminiProtocolTests.swift */,
D626648824BBF22E00DF9B88 /* Info.plist */,
);
path = GeminiProtocolTests;
sourceTree = "<group>";
};
D62664A924BBF26A00DF9B88 /* GeminiFormat */ = { D62664A924BBF26A00DF9B88 /* GeminiFormat */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -173,6 +262,14 @@
/* End PBXGroup section */ /* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */ /* Begin PBXHeadersBuildPhase section */
D626647224BBF22E00DF9B88 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
D626648924BBF22E00DF9B88 /* GeminiProtocol.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
D62664A324BBF26A00DF9B88 /* Headers */ = { D62664A324BBF26A00DF9B88 /* Headers */ = {
isa = PBXHeadersBuildPhase; isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
@ -191,10 +288,12 @@
D626645724BBF1C200DF9B88 /* Sources */, D626645724BBF1C200DF9B88 /* Sources */,
D626645824BBF1C200DF9B88 /* Frameworks */, D626645824BBF1C200DF9B88 /* Frameworks */,
D626645924BBF1C200DF9B88 /* Resources */, D626645924BBF1C200DF9B88 /* Resources */,
D626649124BBF22E00DF9B88 /* Embed Frameworks */,
); );
buildRules = ( buildRules = (
); );
dependencies = ( dependencies = (
D626648B24BBF22E00DF9B88 /* PBXTargetDependency */,
D62664BC24BBF26A00DF9B88 /* PBXTargetDependency */, D62664BC24BBF26A00DF9B88 /* PBXTargetDependency */,
); );
name = Gemini; name = Gemini;
@ -202,6 +301,43 @@
productReference = D626645B24BBF1C200DF9B88 /* Gemini.app */; productReference = D626645B24BBF1C200DF9B88 /* Gemini.app */;
productType = "com.apple.product-type.application"; productType = "com.apple.product-type.application";
}; };
D626647624BBF22E00DF9B88 /* GeminiProtocol */ = {
isa = PBXNativeTarget;
buildConfigurationList = D626648E24BBF22E00DF9B88 /* Build configuration list for PBXNativeTarget "GeminiProtocol" */;
buildPhases = (
D626647224BBF22E00DF9B88 /* Headers */,
D626647324BBF22E00DF9B88 /* Sources */,
D626647424BBF22E00DF9B88 /* Frameworks */,
D626647524BBF22E00DF9B88 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = GeminiProtocol;
productName = GeminiProtocol;
productReference = D626647724BBF22E00DF9B88 /* GeminiProtocol.framework */;
productType = "com.apple.product-type.framework";
};
D626647E24BBF22E00DF9B88 /* GeminiProtocolTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = D626649224BBF22E00DF9B88 /* Build configuration list for PBXNativeTarget "GeminiProtocolTests" */;
buildPhases = (
D626647B24BBF22E00DF9B88 /* Sources */,
D626647C24BBF22E00DF9B88 /* Frameworks */,
D626647D24BBF22E00DF9B88 /* Resources */,
);
buildRules = (
);
dependencies = (
D626648224BBF22E00DF9B88 /* PBXTargetDependency */,
D626648424BBF22E00DF9B88 /* PBXTargetDependency */,
);
name = GeminiProtocolTests;
productName = GeminiProtocolTests;
productReference = D626647F24BBF22E00DF9B88 /* GeminiProtocolTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
D62664A724BBF26A00DF9B88 /* GeminiFormat */ = { D62664A724BBF26A00DF9B88 /* GeminiFormat */ = {
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = D62664BF24BBF26A00DF9B88 /* Build configuration list for PBXNativeTarget "GeminiFormat" */; buildConfigurationList = D62664BF24BBF26A00DF9B88 /* Build configuration list for PBXNativeTarget "GeminiFormat" */;
@ -251,6 +387,14 @@
D626645A24BBF1C200DF9B88 = { D626645A24BBF1C200DF9B88 = {
CreatedOnToolsVersion = 12.0; CreatedOnToolsVersion = 12.0;
}; };
D626647624BBF22E00DF9B88 = {
CreatedOnToolsVersion = 12.0;
LastSwiftMigration = 1200;
};
D626647E24BBF22E00DF9B88 = {
CreatedOnToolsVersion = 12.0;
TestTargetID = D626645A24BBF1C200DF9B88;
};
D62664A724BBF26A00DF9B88 = { D62664A724BBF26A00DF9B88 = {
CreatedOnToolsVersion = 12.0; CreatedOnToolsVersion = 12.0;
LastSwiftMigration = 1200; LastSwiftMigration = 1200;
@ -275,6 +419,8 @@
projectRoot = ""; projectRoot = "";
targets = ( targets = (
D626645A24BBF1C200DF9B88 /* Gemini */, D626645A24BBF1C200DF9B88 /* Gemini */,
D626647624BBF22E00DF9B88 /* GeminiProtocol */,
D626647E24BBF22E00DF9B88 /* GeminiProtocolTests */,
D62664A724BBF26A00DF9B88 /* GeminiFormat */, D62664A724BBF26A00DF9B88 /* GeminiFormat */,
D62664AF24BBF26A00DF9B88 /* GeminiFormatTests */, D62664AF24BBF26A00DF9B88 /* GeminiFormatTests */,
); );
@ -292,6 +438,20 @@
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
D626647524BBF22E00DF9B88 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
D626647D24BBF22E00DF9B88 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
D62664A624BBF26A00DF9B88 /* Resources */ = { D62664A624BBF26A00DF9B88 /* Resources */ = {
isa = PBXResourcesBuildPhase; isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
@ -318,6 +478,28 @@
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
D626647324BBF22E00DF9B88 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
D62664A024BBF24100DF9B88 /* GeminiProtocol.swift in Sources */,
D62664A224BBF24100DF9B88 /* GeminiResponse.swift in Sources */,
D626649E24BBF24100DF9B88 /* GeminiConnection.swift in Sources */,
D62664A124BBF24100DF9B88 /* GeminiRequest.swift in Sources */,
D626649F24BBF24100DF9B88 /* NWParameters+Gemini.swift in Sources */,
D626649D24BBF24100DF9B88 /* Message+Gemini.swift in Sources */,
D626649C24BBF24100DF9B88 /* GeminiResponseHeader.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
D626647B24BBF22E00DF9B88 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
D626648724BBF22E00DF9B88 /* GeminiProtocolTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
D62664A424BBF26A00DF9B88 /* Sources */ = { D62664A424BBF26A00DF9B88 /* Sources */ = {
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
@ -338,6 +520,21 @@
/* End PBXSourcesBuildPhase section */ /* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */ /* Begin PBXTargetDependency section */
D626648224BBF22E00DF9B88 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = D626647624BBF22E00DF9B88 /* GeminiProtocol */;
targetProxy = D626648124BBF22E00DF9B88 /* PBXContainerItemProxy */;
};
D626648424BBF22E00DF9B88 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = D626645A24BBF1C200DF9B88 /* Gemini */;
targetProxy = D626648324BBF22E00DF9B88 /* PBXContainerItemProxy */;
};
D626648B24BBF22E00DF9B88 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = D626647624BBF22E00DF9B88 /* GeminiProtocol */;
targetProxy = D626648A24BBF22E00DF9B88 /* PBXContainerItemProxy */;
};
D62664B324BBF26A00DF9B88 /* PBXTargetDependency */ = { D62664B324BBF26A00DF9B88 /* PBXTargetDependency */ = {
isa = PBXTargetDependency; isa = PBXTargetDependency;
target = D62664A724BBF26A00DF9B88 /* GeminiFormat */; target = D62664A724BBF26A00DF9B88 /* GeminiFormat */;
@ -532,6 +729,105 @@
}; };
name = Release; name = Release;
}; };
D626648F24BBF22E00DF9B88 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = HGYVAQA9FW;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = GeminiProtocol/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
"@loader_path/Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.15;
PRODUCT_BUNDLE_IDENTIFIER = net.shadowfacts.GeminiProtocol;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Debug;
};
D626649024BBF22E00DF9B88 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = HGYVAQA9FW;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = GeminiProtocol/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
"@loader_path/Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.15;
PRODUCT_BUNDLE_IDENTIFIER = net.shadowfacts.GeminiProtocol;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Release;
};
D626649324BBF22E00DF9B88 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = HGYVAQA9FW;
INFOPLIST_FILE = GeminiProtocolTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
"@loader_path/../Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = net.shadowfacts.GeminiProtocolTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Gemini.app/Contents/MacOS/Gemini";
};
name = Debug;
};
D626649424BBF22E00DF9B88 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = HGYVAQA9FW;
INFOPLIST_FILE = GeminiProtocolTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
"@loader_path/../Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = net.shadowfacts.GeminiProtocolTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Gemini.app/Contents/MacOS/Gemini";
};
name = Release;
};
D62664C024BBF26A00DF9B88 /* Debug */ = { D62664C024BBF26A00DF9B88 /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
@ -652,6 +948,24 @@
defaultConfigurationIsVisible = 0; defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release; defaultConfigurationName = Release;
}; };
D626648E24BBF22E00DF9B88 /* Build configuration list for PBXNativeTarget "GeminiProtocol" */ = {
isa = XCConfigurationList;
buildConfigurations = (
D626648F24BBF22E00DF9B88 /* Debug */,
D626649024BBF22E00DF9B88 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
D626649224BBF22E00DF9B88 /* Build configuration list for PBXNativeTarget "GeminiProtocolTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
D626649324BBF22E00DF9B88 /* Debug */,
D626649424BBF22E00DF9B88 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
D62664BF24BBF26A00DF9B88 /* Build configuration list for PBXNativeTarget "GeminiFormat" */ = { D62664BF24BBF26A00DF9B88 /* Build configuration list for PBXNativeTarget "GeminiFormat" */ = {
isa = XCConfigurationList; isa = XCConfigurationList;
buildConfigurations = ( buildConfigurations = (

View File

@ -14,6 +14,11 @@
<key>orderHint</key> <key>orderHint</key>
<integer>2</integer> <integer>2</integer>
</dict> </dict>
<key>GeminiProtocol.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>1</integer>
</dict>
</dict> </dict>
</dict> </dict>
</plist> </plist>

View File

@ -0,0 +1,102 @@
//
// GeminiConnection.swift
// Gemini
//
// Created by Shadowfacts on 7/12/20.
//
import Foundation
import Network
protocol GeminiConnectionDelegate: class {
func connectionReady(_ connection: GeminiConnection)
func connection(_ connection: GeminiConnection, receivedData data: Data?, header: GeminiResponseHeader)
func connectionCompleted(_ connection: GeminiConnection)
}
class GeminiConnection {
weak var delegate: GeminiConnectionDelegate?
private var connection: NWConnection?
init(endpoint: NWEndpoint, delegate: GeminiConnectionDelegate? = nil) {
self.connection = NWConnection(to: endpoint, using: .gemini)
self.delegate = delegate
startConnection()
}
private func startConnection() {
guard let connection = connection else { return }
connection.stateUpdateHandler = { (newState) in
switch newState {
case .ready:
print("\(connection) established")
self.delegate?.connectionReady(self)
case let .failed(error):
print("\(connection) failed: \(error)")
connection.cancel()
default:
break
}
}
// todo: should this be on a background queue?
connection.start(queue: .main)
}
var report: NWConnection.PendingDataTransferReport!
func sendRequest(_ request: GeminiRequest) {
guard let connection = connection else { return }
let message = NWProtocolFramer.Message(geminiRequest: request)
let context = NWConnection.ContentContext(identifier: "GeminiRequest", metadata: [message])
// todo: should this really always be idempotent?
connection.send(content: nil, contentContext: context, isComplete: true, completion: .contentProcessed({ (_) in }))
receiveNextMessage()
report = connection.startDataTransferReport()
}
private func receiveNextMessage() {
guard let connection = connection else { return }
// todo: should this really be .max
connection.receive(minimumIncompleteLength: 1, maximumLength: 65536) { (data, context, isComplete, error) in
// todo: handle error
if let message = context?.protocolMetadata(definition: GeminiProtocol.definition) as? NWProtocolFramer.Message,
let header = message.geminiResponseHeader,
let delegate = self.delegate {
delegate.connection(self, receivedData: data, header: header)
self.receiveNextMessage()
}
if isComplete {
self.delegate?.connectionCompleted(self)
self.report.collect(queue: .main) { (report) in
print(report.debugDescription)
}
// connection.cancel()
}
}
// connection.receiveMessage { (data, context, isComplete, error) in
// if let message = context?.protocolMetadata(definition: GeminiProtocol.definition) as? NWProtocolFramer.Message,
// let header = message.geminiResponseHeader,
// let delegate = self.delegate {
//// let response = GeminiResponse(status: header.status, meta: header.meta, body: data)
// delegate.connection(self, receivedData: data, header: header)
// }
// // todo: error handling
// if let error = error {
// print(error)
// } else if isComplete {
// self.delegate?.connectionCompleted(self)
// connection.cancel()
// } else {
// self.receiveNextMessage()
// }
// }
}
}

View File

@ -0,0 +1,18 @@
//
// GeminiProtocol.h
// GeminiProtocol
//
// Created by Shadowfacts on 7/12/20.
//
#import <Foundation/Foundation.h>
//! Project version number for GeminiProtocol.
FOUNDATION_EXPORT double GeminiProtocolVersionNumber;
//! Project version string for GeminiProtocol.
FOUNDATION_EXPORT const unsigned char GeminiProtocolVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import <GeminiProtocol/PublicHeader.h>

View File

@ -0,0 +1,96 @@
//
// GeminiProtocol.swift
// Gemini
//
// Created by Shadowfacts on 7/12/20.
//
import Network
class GeminiProtocol: NWProtocolFramerImplementation {
static let definition = NWProtocolFramer.Definition(implementation: GeminiProtocol.self)
static let label = "Gemini"
required init(framer: NWProtocolFramer.Instance) {
}
func start(framer: NWProtocolFramer.Instance) -> NWProtocolFramer.StartResult {
return .ready
}
func wakeup(framer: NWProtocolFramer.Instance) {
}
func stop(framer: NWProtocolFramer.Instance) -> Bool {
return true
}
func cleanup(framer: NWProtocolFramer.Instance) {
}
func handleInput(framer: NWProtocolFramer.Instance) -> Int {
while true {
var tempStatusCode: GeminiResponse.StatusCode?
let parsedStatusCodeAndSpace = framer.parseInput(minimumIncompleteLength: 3, maximumLength: 3) { (buffer, isComplete) -> Int in
guard let buffer = buffer,
buffer.count == 3 else { return 0 }
tempStatusCode = GeminiResponse.StatusCode(buffer)
return 3
}
guard parsedStatusCodeAndSpace, let statusCode = tempStatusCode else {
return 3
}
var tempMeta: String?
// Minimum length is 2 bytes, spec does not say meta string is required
let parsedMeta = framer.parseInput(minimumIncompleteLength: 2, maximumLength: 1024 + 2) { (buffer, isComplete) -> Int in
guard let buffer = buffer,
buffer.count >= 2 else { return 0 }
let lastPossibleCRIndex = buffer.index(before: buffer.index(before: buffer.endIndex))
var index = buffer.startIndex
var found = false
while index <= lastPossibleCRIndex {
// <CR><LF>
if buffer[index] == 13 && buffer[buffer.index(after: index)] == 10 {
found = true
break
}
index = buffer.index(after: index)
}
guard found else { fatalError("Didn't find <CR><LF> in buffer. Meta string was longer than 1024 bytes") }
tempMeta = String(bytes: buffer[..<index], encoding: .utf8)
return buffer.startIndex.distance(to: index)
}
guard parsedMeta, let meta = tempMeta else {
// todo: what should actually be returned here? the minimum number of bytes necessary?
return 2
}
let header = GeminiResponseHeader(status: statusCode, meta: meta)
let message = NWProtocolFramer.Message(geminiResponseHeader: header)
if !framer.deliverInputNoCopy(length: 2 + 1 + meta.utf8.count + 2, message: message, isComplete: true) {
// todo: why return zero here?
return 0
}
}
}
func handleOutput(framer: NWProtocolFramer.Instance, message: NWProtocolFramer.Message, messageLength: Int, isComplete: Bool) {
guard let request = message.geminiRequest else { fatalError("GeminiProtocol can't send message that doesn't have an associated GeminiRequest") }
framer.writeOutput(data: request.data)
}
}
fileprivate extension GeminiResponse.StatusCode {
init?(_ buffer: UnsafeMutableRawBufferPointer) {
guard let str = String(bytes: buffer[...buffer.index(after: buffer.startIndex)], encoding: .utf8),
let value = Int(str, radix: 10) else { return nil }
self.init(rawValue: value)
}
}

View File

@ -0,0 +1,31 @@
//
// GeminiRequest.swift
// Gemini
//
// Created by Shadowfacts on 7/12/20.
//
import Foundation
struct GeminiRequest {
let url: URL
init(url: URL) throws {
if url.absoluteString.count > 1024 {
throw Error.urlTooLong
}
self.url = url
}
var data: Data {
var data = url.absoluteString.data(using: .utf8)!
data.append(contentsOf: [13, 10]) // <CR><LF>
return data
}
}
extension GeminiRequest {
enum Error: Swift.Error {
case urlTooLong
}
}

View File

@ -0,0 +1,132 @@
//
// GeminiResponse.swift
// Gemini
//
// Created by Shadowfacts on 7/12/20.
//
import Foundation
import UniformTypeIdentifiers
struct GeminiResponse {
let status: StatusCode
let meta: String
let body: Data?
// Helpers
var rawMimeType: String? {
guard status.isSuccess else { return nil }
return meta.trimmingCharacters(in: .whitespacesAndNewlines)
}
var mimeType: String? {
guard let rawMimeType = rawMimeType else { return nil }
return rawMimeType.split(separator: ";").first?.trimmingCharacters(in: .whitespaces)
}
var mimeTypeParameters: [String: String]? {
guard let rawMimeType = rawMimeType else { return nil }
return rawMimeType.split(separator: ";").dropFirst().reduce(into: [String: String]()) { (parameters, parameter) in
let parts = parameter.split(separator: "=").map { $0.trimmingCharacters(in: .whitespaces) }
precondition(parts.count == 2)
parameters[parts[0].lowercased()] = parts[1]
}
}
@available(macOS 10.16, *)
var utiType: UTType? {
guard let mimeType = mimeType else { return nil }
return UTType.types(tag: mimeType, tagClass: .mimeType, conformingTo: nil).first
}
var bodyText: String? {
guard let body = body, let parameters = mimeTypeParameters else { return nil }
let encoding: String.Encoding
switch parameters["charset"]?.lowercased() {
case nil, "utf-8":
// The Gemini spec defines UTF-8 to be the default charset.
encoding = .utf8
case "us-ascii":
encoding = .ascii
default:
// todo: log warning
encoding = .utf8
}
return String(data: body, encoding: encoding)
}
}
extension GeminiResponse {
enum StatusCode: Int {
// All statuses and subtypes
case input = 10
case sensitiveInput = 11
case success = 20
case temporaryRedirect = 30
case permanentRedirect = 31
case temporaryFailure = 40
case serverUnavailable = 41
case cgiError = 42
case proxyError = 43
case slowDown = 44
case permanentFailure = 50
case notFound = 51
case gone = 52
case proxyRequestRefused = 53
case badRequest = 59
case clientCertificateRequested = 60
case certificateNotAuthorised = 61
case certificateNotValid = 62
// Status type helpers
var isInput: Bool { rawValue / 10 == 1 }
var isSuccess: Bool { rawValue / 10 == 2 }
var isRedirect: Bool { rawValue / 10 == 3 }
var isTemporaryFailure: Bool { rawValue / 10 == 4 }
var isPermanentFailure: Bool { rawValue / 10 == 5 }
var isClientCertificateRequired: Bool { rawValue / 10 == 6 }
// Other helpers
var isFailure: Bool { isTemporaryFailure || isPermanentFailure }
}
}
extension GeminiResponse.StatusCode: CustomStringConvertible {
var description: String {
switch self {
case .input:
return "input"
case .sensitiveInput:
return "sensitiveInput"
case .success:
return "success"
case .temporaryRedirect:
return "temporaryRedirect"
case .permanentRedirect:
return "permanentRedirect"
case .temporaryFailure:
return "temporaryFailure"
case .serverUnavailable:
return "serverUnavailable"
case .cgiError:
return "cgiError"
case .proxyError:
return "proxyError"
case .slowDown:
return "slowDown"
case .permanentFailure:
return "permanentFailure"
case .notFound:
return "notFound"
case .gone:
return "gone"
case .proxyRequestRefused:
return "proxyRequestRefused"
case .badRequest:
return "badRequest"
case .clientCertificateRequested:
return "clientCertificateRequested"
case .certificateNotAuthorised:
return "certificateNotAuthorised"
case .certificateNotValid:
return "certificateNotValid"
}
}
}

View File

@ -0,0 +1,13 @@
//
// GeminiResponseHeader.swift
// Gemini
//
// Created by Shadowfacts on 7/12/20.
//
import Foundation
struct GeminiResponseHeader {
let status: GeminiResponse.StatusCode
let meta: String
}

22
GeminiProtocol/Info.plist Normal file
View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
</dict>
</plist>

View File

@ -0,0 +1,33 @@
//
// Message+Gemini.swift
// Gemini
//
// Created by Shadowfacts on 7/12/20.
//
import Network
private let requestKey = "gemini_request"
private let responseHeaderKey = "gemini_response_header"
extension NWProtocolFramer.Message {
convenience init(geminiRequest request: GeminiRequest) {
self.init(definition: GeminiProtocol.definition)
self[requestKey] = request
}
convenience init(geminiResponseHeader header: GeminiResponseHeader) {
self.init(definition: GeminiProtocol.definition)
self[responseHeaderKey] = header
}
var geminiRequest: GeminiRequest? {
self[requestKey] as? GeminiRequest
}
var geminiResponseHeader: GeminiResponseHeader? {
self[responseHeaderKey] as? GeminiResponseHeader
}
}

View File

@ -0,0 +1,42 @@
//
// NWParameters+Gemini.swift
// Gemini
//
// Created by Shadowfacts on 7/12/20.
//
import Network
extension NWParameters {
static var gemini: NWParameters {
let tlsOptions = geminiTLSOptions
let tcpOptions = NWProtocolTCP.Options()
let parameters = NWParameters(tls: tlsOptions, tcp: tcpOptions)
let geminiOptions = NWProtocolFramer.Options(definition: GeminiProtocol.definition)
parameters.defaultProtocolStack.applicationProtocols.insert(geminiOptions, at: 0)
return parameters
}
private static var geminiTLSOptions: NWProtocolTLS.Options {
let options = NWProtocolTLS.Options()
sec_protocol_options_set_min_tls_protocol_version(options.securityProtocolOptions, .TLSv12)
// based on https://developer.apple.com/forums/thread/104018
sec_protocol_options_set_verify_block(options.securityProtocolOptions, { (sec_protocol_metadata, sec_trust, sec_protocol_verify_complete) in
let trust = sec_trust_copy_ref(sec_trust).takeRetainedValue()
var error: CFError?
if SecTrustEvaluateWithError(trust, &error) {
sec_protocol_verify_complete(true)
} else {
// todo: prompt user to trust cert on first use
sec_protocol_verify_complete(true)
}
}, .main)
return options
}
}

View File

@ -0,0 +1,33 @@
//
// GeminiProtocolTests.swift
// GeminiProtocolTests
//
// Created by Shadowfacts on 7/12/20.
//
import XCTest
@testable import GeminiProtocol
class GeminiProtocolTests: XCTestCase {
override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDownWithError() throws {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
func testExample() throws {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct results.
}
func testPerformanceExample() throws {
// This is an example of a performance test case.
self.measure {
// Put the code you want to measure the time of here.
}
}
}

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>