mirror of
https://github.com/StikDebug/StikDebug.git
synced 2026-10-16 04:20:30 +00:00
Show stiktool capabilities before first run. Fix instproxy get apps issue.
This commit is contained in:
parent
44d81afa4e
commit
77b8810e6b
7 changed files with 343 additions and 72 deletions
|
|
@ -12,21 +12,34 @@
|
|||
@end
|
||||
|
||||
|
||||
NSDictionary *dictionaryFromPlistData(NSData *plistData, NSError **error) {
|
||||
if (!plistData) {
|
||||
NSDictionary *dictionaryFromPlist(plist_t plist, NSError **error) {
|
||||
if (!plist) {
|
||||
if (error) {
|
||||
*error = [NSError errorWithDomain:@"PlistConversionErrorDomain"
|
||||
code:1001
|
||||
userInfo:@{NSLocalizedDescriptionKey: @"Input plist data is nil."}];
|
||||
userInfo:@{NSLocalizedDescriptionKey: @"Input plist is nil."}];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
char* buf = 0;
|
||||
uint32_t plistlen = 0;
|
||||
// try bin plist first, if failed, we try xml plist
|
||||
plist_to_bin(plist, &buf, &plistlen);
|
||||
|
||||
NSPropertyListFormat format;
|
||||
NSDictionary *result = [NSPropertyListSerialization propertyListWithData:plistData
|
||||
options:NSPropertyListImmutable
|
||||
format:&format
|
||||
error:error];
|
||||
NSDictionary *result = [NSPropertyListSerialization propertyListWithData:[NSData dataWithBytes:buf length:plistlen]
|
||||
options:NSPropertyListImmutable format:&format error:error];
|
||||
plist_mem_free(buf);
|
||||
if (![result isKindOfClass:[NSDictionary class]]) {
|
||||
plist_to_xml(plist, &buf, &plistlen);
|
||||
if (error) {
|
||||
*error = nil;
|
||||
}
|
||||
result = [NSPropertyListSerialization propertyListWithData:[NSData dataWithBytes:buf length:plistlen]
|
||||
options:NSPropertyListImmutable format:&format error:error];
|
||||
plist_free(buf);
|
||||
}
|
||||
|
||||
if (![result isKindOfClass:[NSDictionary class]]) {
|
||||
if (error && !*error) {
|
||||
*error = [NSError errorWithDomain:@"PlistConversionErrorDomain"
|
||||
|
|
@ -78,11 +91,12 @@ const char** cstrArrFromNSArray(NSArray* arr, int* validCount) {
|
|||
int maxDataId;
|
||||
}
|
||||
|
||||
- (instancetype)initWithContext:(JSContext*)context {
|
||||
- (instancetype)initWithContext:(JSContext*)context allowedFunctions:(NSArray<NSString*>*)allowedFunctions {
|
||||
maxHandleId = 0;
|
||||
maxDataId = 0;
|
||||
handles = [[NSMutableDictionary alloc] init];
|
||||
dataPool = [[NSMutableDictionary alloc] init];
|
||||
self->allowedFunctions = allowedFunctions;
|
||||
self->context = context;
|
||||
return self;
|
||||
}
|
||||
|
|
@ -150,6 +164,11 @@ const char** cstrArrFromNSArray(NSArray* arr, int* validCount) {
|
|||
return;
|
||||
}
|
||||
|
||||
if(![self->allowedFunctions containsObject:message[@"command"]]) {
|
||||
[rejectFunc callWithArguments:@[[NSString stringWithFormat:@"calling %@ is not allowed! Declare it in toolInfo.json", message[@"command"]]]];
|
||||
return;
|
||||
}
|
||||
|
||||
NSString* handlerSelectorStr = [NSString stringWithFormat:@"%@WithBody:replyHandler:", message[@"command"]];
|
||||
if(![self respondsToSelector:NSSelectorFromString(handlerSelectorStr)]) {
|
||||
[rejectFunc callWithArguments:@[@"Invalid idevice function!"]];
|
||||
|
|
|
|||
|
|
@ -69,23 +69,24 @@ void installationProxyCallback(uint64_t progress, struct InstallationProxyCallba
|
|||
}
|
||||
|
||||
plist_t *app_list = (plist_t *)apps;
|
||||
NSMutableArray* ans = [[NSMutableArray alloc] init];
|
||||
for(int i = 0; i < apps_len; ++i) {
|
||||
char* buf = 0;
|
||||
uint32_t plistlen = 0;
|
||||
plist_to_bin(app_list[i], &buf, &plistlen);
|
||||
NSError* err2 = 0;
|
||||
NSDictionary* appDict = dictionaryFromPlistData([NSData dataWithBytes:buf length:plistlen], &err2);
|
||||
plist_mem_free(buf);
|
||||
if(err2) {
|
||||
replyHandler(nil, @"failed to parse plist data");
|
||||
return;
|
||||
do {
|
||||
NSMutableArray* ans = [[NSMutableArray alloc] init];
|
||||
for(int i = 0; i < apps_len; ++i) {
|
||||
NSError* err2 = 0;
|
||||
NSDictionary* appDict = dictionaryFromPlist(app_list[i], &err2);
|
||||
|
||||
if(err2) {
|
||||
replyHandler(nil, @"failed to parse plist data");
|
||||
break;
|
||||
}
|
||||
[ans addObject:appDict];
|
||||
}
|
||||
[ans addObject:appDict];
|
||||
replyHandler(ans, nil);
|
||||
} while(0);
|
||||
|
||||
for(int i = 0; i < apps_len; ++i) {
|
||||
plist_free(app_list[i]);
|
||||
}
|
||||
replyHandler(ans, nil);
|
||||
|
||||
|
||||
}
|
||||
|
||||
- (void)installation_proxy_installWithBody:(NSDictionary *)body replyHandler:(nonnull void (^)(id _Nullable, NSString * _Nullable))replyHandler {
|
||||
|
|
@ -266,33 +267,33 @@ void installationProxyCallback(uint64_t progress, struct InstallationProxyCallba
|
|||
plist_from_memory((void*)[optionsNSData bytes], (uint32_t)[optionsNSData length], &optionsPlist, 0);
|
||||
}
|
||||
|
||||
plist_t *apps = NULL;
|
||||
plist_t *app_list = NULL;
|
||||
size_t apps_len = 0;
|
||||
IdeviceFfiError* err = installation_proxy_browse(client, optionsPlist, &apps, &apps_len);
|
||||
IdeviceFfiError* err = installation_proxy_browse(client, optionsPlist, &app_list, &apps_len);
|
||||
|
||||
if (err) {
|
||||
replyHandler(nil, [self errFreeFromIdeviceFfiError:err]);
|
||||
return;
|
||||
}
|
||||
|
||||
NSMutableArray* ans = [[NSMutableArray alloc] init];
|
||||
do {
|
||||
NSMutableArray* ans = [[NSMutableArray alloc] init];
|
||||
for(int i = 0; i < apps_len; ++i) {
|
||||
NSError* err2 = 0;
|
||||
NSDictionary* appDict = dictionaryFromPlist(app_list[i], &err2);
|
||||
|
||||
if(err2) {
|
||||
replyHandler(nil, @"failed to parse plist data");
|
||||
break;
|
||||
}
|
||||
[ans addObject:appDict];
|
||||
}
|
||||
replyHandler(ans, nil);
|
||||
} while(0);
|
||||
|
||||
for(int i = 0; i < apps_len; ++i) {
|
||||
char* buf = 0;
|
||||
uint32_t plistlen = 0;
|
||||
plist_to_bin(apps[i], &buf, &plistlen);
|
||||
NSError* err2 = 0;
|
||||
NSDictionary* appDict = dictionaryFromPlistData([NSData dataWithBytes:buf length:plistlen], &err2);
|
||||
plist_mem_free(buf);
|
||||
if(err2) {
|
||||
replyHandler(nil, @"failed to parse plist data");
|
||||
return;
|
||||
}
|
||||
[ans addObject:appDict];
|
||||
if([ans count] >= 100) {
|
||||
break;
|
||||
}
|
||||
plist_free(app_list[i]);
|
||||
}
|
||||
replyHandler(ans, nil);
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,9 +23,10 @@
|
|||
@interface IDeviceJSBridge : NSObject {
|
||||
NSMutableDictionary<NSNumber*, IDeviceHandle*>* handles;
|
||||
NSMutableDictionary<NSNumber*, NSData*>* dataPool;
|
||||
NSArray<NSString*>* allowedFunctions;
|
||||
JSContext* context;
|
||||
}
|
||||
- (instancetype)initWithContext:(JSContext*)context;
|
||||
- (instancetype)initWithContext:(JSContext*)context allowedFunctions:(NSArray<NSString*>*)allowedFunctions;
|
||||
- (void)didReceiveScriptMessage:(NSDictionary *)message resolve:(JSValue*)resolveFunc reject:(JSValue*)rejectFunc;
|
||||
- (void)cleanUp;
|
||||
- (NSString*)errFreeFromIdeviceFfiError:(IdeviceFfiError*)err;
|
||||
|
|
@ -36,6 +37,6 @@
|
|||
@end
|
||||
|
||||
|
||||
NSDictionary *dictionaryFromPlistData(NSData *plistData, NSError **error);
|
||||
NSDictionary *dictionaryFromPlist(plist_t plist, NSError **error);
|
||||
NSData *plistDataFromDictionary(NSDictionary *dictionary, NSError **error);
|
||||
const char** cstrArrFromNSArray(NSArray* arr, int* validCount);
|
||||
|
|
|
|||
|
|
@ -2,6 +2,28 @@ import SwiftUI
|
|||
import UniformTypeIdentifiers
|
||||
import UIKit
|
||||
|
||||
class ToolCombo : Hashable, ObservableObject {
|
||||
static func == (lhs: ToolCombo, rhs: ToolCombo) -> Bool {
|
||||
return lhs === rhs
|
||||
}
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(ObjectIdentifier(self))
|
||||
}
|
||||
|
||||
init(tool: MiniToolBundle, info: ToolInfo) {
|
||||
self.tool = tool
|
||||
self.info = info
|
||||
}
|
||||
|
||||
init() {
|
||||
self.tool = nil
|
||||
self.info = nil
|
||||
}
|
||||
|
||||
@Published var tool: MiniToolBundle?
|
||||
@Published var info: ToolInfo?
|
||||
}
|
||||
|
||||
struct MiniToolListView: View {
|
||||
@StateObject private var store = MiniToolStore()
|
||||
@State private var searchText = ""
|
||||
|
|
@ -11,6 +33,9 @@ struct MiniToolListView: View {
|
|||
@State private var alertVisible = false
|
||||
@State private var alertTitle = ""
|
||||
@State private var alertMessage = ""
|
||||
@State private var showInfoSheet = false
|
||||
@State private var navigationToolCombo: ToolCombo?
|
||||
@StateObject private var pendingToolCombo: ToolCombo = ToolCombo()
|
||||
@AppStorage("appTheme") private var appThemeRaw: String = AppTheme.system.rawValue
|
||||
@Environment(\.themeExpansionManager) private var themeExpansion
|
||||
@Environment(\.colorScheme) private var colorScheme
|
||||
|
|
@ -102,8 +127,14 @@ struct MiniToolListView: View {
|
|||
presentError(title: "Import Failed", message: error.localizedDescription)
|
||||
}
|
||||
}
|
||||
.navigationDestination(item: $navigationToolCombo) { combo in
|
||||
MiniToolRunnerView(tool: combo.tool!, toolInfo: combo.info!)
|
||||
}
|
||||
}
|
||||
.preferredColorScheme(preferredScheme)
|
||||
.sheet(isPresented: $showInfoSheet) {
|
||||
toolInfoSheet
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Cards
|
||||
|
|
@ -130,8 +161,8 @@ struct MiniToolListView: View {
|
|||
|
||||
private func toolRow(_ tool: MiniToolBundle) -> some View {
|
||||
HStack(spacing: 12) {
|
||||
NavigationLink {
|
||||
MiniToolRunnerView(tool: tool)
|
||||
Button {
|
||||
handleRun(tool)
|
||||
} label: {
|
||||
HStack(spacing: 12) {
|
||||
Image(systemName: "shippingbox.fill")
|
||||
|
|
@ -146,29 +177,28 @@ struct MiniToolListView: View {
|
|||
.foregroundColor(.secondary)
|
||||
.lineLimit(1)
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
NavigationLink {
|
||||
MiniToolEditorView(tool: tool)
|
||||
} label: {
|
||||
Image(systemName: "pencil")
|
||||
.foregroundColor(.primary)
|
||||
}
|
||||
.buttonStyle(.borderless)
|
||||
|
||||
Button(role: .destructive) {
|
||||
pendingDelete = tool
|
||||
showDeleteConfirmation = true
|
||||
} label: {
|
||||
Image(systemName: "trash")
|
||||
.foregroundColor(.red)
|
||||
}
|
||||
.buttonStyle(.borderless)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
|
||||
NavigationLink {
|
||||
MiniToolEditorView(tool: tool)
|
||||
} label: {
|
||||
Image(systemName: "pencil")
|
||||
.foregroundColor(.primary)
|
||||
}
|
||||
.buttonStyle(.borderless)
|
||||
|
||||
Button(role: .destructive) {
|
||||
pendingDelete = tool
|
||||
showDeleteConfirmation = true
|
||||
} label: {
|
||||
Image(systemName: "trash")
|
||||
.foregroundColor(.red)
|
||||
}
|
||||
.buttonStyle(.borderless)
|
||||
|
||||
}
|
||||
.padding(20)
|
||||
.background(glassyBackground)
|
||||
|
|
@ -253,4 +283,158 @@ struct MiniToolListView: View {
|
|||
private func copy(_ text: String) {
|
||||
UIPasteboard.general.string = text
|
||||
}
|
||||
|
||||
private func handleRun(_ tool: MiniToolBundle) {
|
||||
do {
|
||||
guard let info = try loadToolInfo(for: tool) else {
|
||||
presentError(title: "Mini Tool", message: "toolInfo.json is missing or unreadable for this tool.")
|
||||
return
|
||||
}
|
||||
if try isTrusted(tool) {
|
||||
navigationToolCombo = ToolCombo(tool: tool, info: info)
|
||||
return
|
||||
}
|
||||
|
||||
pendingToolCombo.info = info
|
||||
pendingToolCombo.tool = tool
|
||||
print("set pendingToolInfo")
|
||||
showInfoSheet = true
|
||||
print("pendingToolInfo = true")
|
||||
} catch {
|
||||
presentError(title: "Mini Tool", message: error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
private func loadToolInfo(for tool: MiniToolBundle) throws -> ToolInfo? {
|
||||
let infoURL = tool.url.appendingPathComponent("toolInfo.json")
|
||||
guard FileManager.default.fileExists(atPath: infoURL.path) else { return nil }
|
||||
let data = try Data(contentsOf: infoURL)
|
||||
return try JSONDecoder().decode(ToolInfo.self, from: data)
|
||||
}
|
||||
|
||||
private func isTrusted(_ tool: MiniToolBundle) throws -> Bool {
|
||||
guard let uuid = UIDevice.current.identifierForVendor?.uuidString else { return false }
|
||||
let checkURL = tool.url.appendingPathComponent("check")
|
||||
guard FileManager.default.fileExists(atPath: checkURL.path) else { return false }
|
||||
let contents = try String(contentsOf: checkURL, encoding: .utf8).trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
return contents == uuid
|
||||
}
|
||||
|
||||
private func authorizeAndRun() {
|
||||
guard let tool = pendingToolCombo.tool else {
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
try writeCheck(for: tool)
|
||||
navigationToolCombo = pendingToolCombo
|
||||
} catch {
|
||||
presentError(title: "Mini Tool", message: error.localizedDescription)
|
||||
}
|
||||
resetPending()
|
||||
}
|
||||
|
||||
private func writeCheck(for tool: MiniToolBundle) throws {
|
||||
guard let uuid = UIDevice.current.identifierForVendor?.uuidString else {
|
||||
throw NSError(domain: "MiniTool", code: -1, userInfo: [NSLocalizedDescriptionKey: "identifierForVendor is unavailable."])
|
||||
}
|
||||
let checkURL = tool.url.appendingPathComponent("check")
|
||||
try uuid.write(to: checkURL, atomically: true, encoding: .utf8)
|
||||
}
|
||||
|
||||
private func resetPending() {
|
||||
showInfoSheet = false
|
||||
// pendingTool = MiniToolBundle()
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var toolInfoSheet: some View {
|
||||
if let tool = pendingToolCombo.tool, let info = pendingToolCombo.info {
|
||||
ToolInfoSheet(tool: tool, info: info) {
|
||||
authorizeAndRun()
|
||||
} onCancel: {
|
||||
resetPending()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct ToolInfoSheet: View {
|
||||
let tool: MiniToolBundle
|
||||
let info: ToolInfo
|
||||
let onAllow: () -> Void
|
||||
let onCancel: () -> Void
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(tool.name)
|
||||
.font(.title2.weight(.semibold))
|
||||
Text(info.desc)
|
||||
.font(.body)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Label("Author", systemImage: "person")
|
||||
.font(.subheadline.weight(.semibold))
|
||||
Text(info.author)
|
||||
.font(.body)
|
||||
}
|
||||
|
||||
if let capabilities = info.capabilities {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Label("Capabilities", systemImage: "checkmark.shield")
|
||||
.font(.subheadline.weight(.semibold))
|
||||
CapabilityRow(title: "Internet Access", enabled: capabilities.internetAccess ?? false)
|
||||
}
|
||||
}
|
||||
|
||||
if let functions = info.requiredIDeviceFunctions, !functions.isEmpty {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Label("Required iDevice Functions", systemImage: "bolt.horizontal.circle")
|
||||
.font(.subheadline.weight(.semibold))
|
||||
ForEach(functions, id: \.self) { function in
|
||||
HStack(spacing: 8) {
|
||||
Image(systemName: "bolt.fill")
|
||||
.foregroundColor(.blue)
|
||||
Text(function)
|
||||
.font(.body)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(20)
|
||||
}
|
||||
.navigationTitle("Allow Tool?")
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .cancellationAction) {
|
||||
Button("Cancel") { onCancel() }
|
||||
}
|
||||
ToolbarItem(placement: .confirmationAction) {
|
||||
Button("Allow & Run") { onAllow() }
|
||||
.bold()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct CapabilityRow: View {
|
||||
let title: String
|
||||
let enabled: Bool
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 8) {
|
||||
Image(systemName: enabled ? "checkmark.circle.fill" : "xmark.circle")
|
||||
.foregroundColor(enabled ? .green : .red)
|
||||
Text(title)
|
||||
.font(.body)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,41 @@
|
|||
import Foundation
|
||||
|
||||
struct MiniToolBundle: Identifiable, Hashable {
|
||||
let url: URL
|
||||
struct ToolInfo: Decodable {
|
||||
struct Capabilities: Decodable {
|
||||
let internetAccess: Bool?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case internetAccess = "internet_access"
|
||||
}
|
||||
}
|
||||
|
||||
let author: String
|
||||
let desc: String
|
||||
let capabilities: Capabilities?
|
||||
let requiredIDeviceFunctions: [String]?
|
||||
|
||||
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case author
|
||||
case desc
|
||||
case capabilities
|
||||
case requiredIDeviceFunctions = "required_idevice_functions"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class MiniToolBundle: ObservableObject, Identifiable {
|
||||
static func == (lhs: MiniToolBundle, rhs: MiniToolBundle) -> Bool {
|
||||
return lhs.name == rhs.name
|
||||
}
|
||||
|
||||
@Published var url: URL
|
||||
|
||||
init(url: URL) {
|
||||
self.url = url
|
||||
}
|
||||
|
||||
var id: String { url.lastPathComponent }
|
||||
var name: String { url.deletingPathExtension().lastPathComponent }
|
||||
var indexURL: URL { url.appendingPathComponent("index.html") }
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import WebKit
|
|||
|
||||
struct MiniToolRunnerView: View {
|
||||
let tool: MiniToolBundle
|
||||
let toolInfo: ToolInfo
|
||||
@StateObject private var runtime: MiniToolRuntime
|
||||
@State private var showLogs = false
|
||||
@State private var initiated = false
|
||||
|
|
@ -13,9 +14,10 @@ struct MiniToolRunnerView: View {
|
|||
private var backgroundStyle: BackgroundStyle { themeExpansion?.backgroundStyle(for: appThemeRaw) ?? AppTheme.system.backgroundStyle }
|
||||
private var preferredScheme: ColorScheme? { themeExpansion?.preferredColorScheme(for: appThemeRaw) }
|
||||
|
||||
init(tool: MiniToolBundle) {
|
||||
init(tool: MiniToolBundle, toolInfo: ToolInfo) {
|
||||
self.tool = tool
|
||||
_runtime = StateObject(wrappedValue: MiniToolRuntime(tool: tool))
|
||||
self.toolInfo = toolInfo
|
||||
_runtime = StateObject(wrappedValue: MiniToolRuntime(tool: tool, toolInfo: toolInfo))
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import JavaScriptCore
|
|||
|
||||
final class MiniToolRuntime: NSObject, ObservableObject {
|
||||
let tool: MiniToolBundle
|
||||
let toolInfo: ToolInfo
|
||||
@Published var logs: [String] = []
|
||||
@Published var isReady: Bool = false
|
||||
|
||||
|
|
@ -18,8 +19,9 @@ final class MiniToolRuntime: NSObject, ObservableObject {
|
|||
|
||||
private var ideviceJSBridge : IDeviceJSBridge? = nil
|
||||
|
||||
init(tool: MiniToolBundle) {
|
||||
init(tool: MiniToolBundle, toolInfo: ToolInfo) {
|
||||
self.tool = tool
|
||||
self.toolInfo = toolInfo
|
||||
super.init()
|
||||
let configuration = WKWebViewConfiguration()
|
||||
let controller = WKUserContentController()
|
||||
|
|
@ -64,7 +66,7 @@ final class MiniToolRuntime: NSObject, ObservableObject {
|
|||
|
||||
private func loadBackground() {
|
||||
context = JSContext()
|
||||
ideviceJSBridge = IDeviceJSBridge(context: context)
|
||||
ideviceJSBridge = IDeviceJSBridge(context: context, allowedFunctions: toolInfo.requiredIDeviceFunctions ?? [])
|
||||
context?.exceptionHandler = { [weak self] _, exception in
|
||||
if let message = exception?.toString() {
|
||||
self?.appendLog("Background exception: \(message)")
|
||||
|
|
@ -155,8 +157,18 @@ extension MiniToolRuntime : WKURLSchemeHandler {
|
|||
let path = url.path.isEmpty ? "index.html" : url.path
|
||||
|
||||
let fileURL = tool.url
|
||||
.appendingPathComponent(path)
|
||||
|
||||
.appendingPathComponent(path).standardizedFileURL
|
||||
|
||||
if fileURL.path().hasPrefix(tool.url.path()) {
|
||||
urlSchemeTask.didFailWithError(NSError(domain: "Path traversal is not allowed.", code: -1))
|
||||
return
|
||||
}
|
||||
|
||||
if fileURL.path() == tool.url.appending(path: "check").path() {
|
||||
urlSchemeTask.didFailWithError(NSError(domain: "Reading this file is not allowed.", code: -1))
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
let data = try Data(contentsOf: fileURL)
|
||||
let mimeType : String
|
||||
|
|
@ -206,6 +218,20 @@ extension MiniToolRuntime: WKScriptMessageHandler {
|
|||
// MARK: - WKNavigationDelegate
|
||||
|
||||
extension MiniToolRuntime: WKNavigationDelegate {
|
||||
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction) async -> WKNavigationActionPolicy {
|
||||
// Don't allow loading other contents
|
||||
guard let url = navigationAction.request.url else {
|
||||
return WKNavigationActionPolicy.cancel
|
||||
}
|
||||
guard let scheme = url.scheme, scheme == "app" else {
|
||||
return WKNavigationActionPolicy.cancel
|
||||
}
|
||||
guard let host = url.host(), host == tool.getHostName() else {
|
||||
return WKNavigationActionPolicy.cancel
|
||||
}
|
||||
return WKNavigationActionPolicy.allow
|
||||
}
|
||||
|
||||
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
|
||||
isReady = true
|
||||
deliverToBackground(["type": "ui-ready", "tool": tool.name])
|
||||
|
|
@ -462,6 +488,11 @@ extension MiniToolRuntime {
|
|||
|
||||
private extension MiniToolRuntime {
|
||||
func handleAppXHRMessage(_ payload: [String: Any]) {
|
||||
guard let capabilities = toolInfo.capabilities, capabilities.internetAccess ?? false else {
|
||||
appendLog("Internet access is disabled! To enable it, enable internetAccess in toolInfo.json")
|
||||
return
|
||||
}
|
||||
|
||||
guard let id = payload["id"] as? String else {
|
||||
appendLog("AppXHR missing id")
|
||||
return
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue