Various fixes

This commit is contained in:
Ilya Laktyushin 2026-04-27 01:36:16 +02:00
parent adc0d5ba6e
commit c4e1049396
5 changed files with 113 additions and 28 deletions

View file

@ -67,6 +67,8 @@ public final class ChatMessageItemAssociatedData: Equatable {
public let isSuspiciousPeer: Bool
public let showTextAsPlaceholder: Bool
public let accountCountry: String?
public let isParticipant: Bool
public let invitedOn: Int32?
public init(
automaticDownloadPeerType: MediaAutoDownloadPeerType,
@ -104,7 +106,9 @@ public final class ChatMessageItemAssociatedData: Equatable {
showSensitiveContent: Bool = false,
isSuspiciousPeer: Bool = false,
showTextAsPlaceholder: Bool = false,
accountCountry: String? = nil
accountCountry: String? = nil,
isParticipant: Bool = false,
invitedOn: Int32? = nil
) {
self.automaticDownloadPeerType = automaticDownloadPeerType
self.automaticDownloadPeerId = automaticDownloadPeerId
@ -142,6 +146,8 @@ public final class ChatMessageItemAssociatedData: Equatable {
self.isSuspiciousPeer = isSuspiciousPeer
self.showTextAsPlaceholder = showTextAsPlaceholder
self.accountCountry = accountCountry
self.isParticipant = isParticipant
self.invitedOn = invitedOn
}
public static func == (lhs: ChatMessageItemAssociatedData, rhs: ChatMessageItemAssociatedData) -> Bool {
@ -241,6 +247,12 @@ public final class ChatMessageItemAssociatedData: Equatable {
if lhs.accountCountry != rhs.accountCountry {
return false
}
if lhs.isParticipant != rhs.isParticipant {
return false
}
if lhs.invitedOn != rhs.invitedOn {
return false
}
return true
}
}

View file

@ -2690,6 +2690,14 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
if !poll.countries.isEmpty, let accountCountry = item.associatedData.accountCountry, !poll.countries.contains(accountCountry) {
isRestricted = true
}
if poll.restrictToSubscribers {
let period: Int32 = item.context.account.testingEnvironment ? 5 * 60 : 24 * 60 * 60
if !item.associatedData.isParticipant {
isRestricted = true
} else if let invitedOn = item.associatedData.invitedOn, invitedOn + period > Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) {
isRestricted = true
}
}
orderedPollOptions = resolvedOptionOrder(for: item)

View file

@ -373,6 +373,8 @@ private func extractAssociatedData(
var automaticMediaDownloadPeerType: MediaAutoDownloadPeerType = .channel
var contactsPeerIds: Set<PeerId> = Set()
var channelDiscussionGroup: ChatMessageItemAssociatedData.ChannelDiscussionGroupStatus = .unknown
var isParticipant = false
var invitedOn: Int32?
if case let .peer(peerId) = chatLocation {
automaticDownloadPeerId = peerId
@ -396,11 +398,15 @@ private func extractAssociatedData(
} else if peerId.namespace == Namespaces.Peer.CloudChannel {
for entry in view.additionalData {
if case let .peer(_, value) = entry {
if let channel = value as? TelegramChannel, case .group = channel.info {
automaticMediaDownloadPeerType = .group
if let channel = value as? TelegramChannel {
if case .group = channel.info {
automaticMediaDownloadPeerType = .group
}
isParticipant = channel.participationStatus == .member
}
} else if case let .cachedPeerData(dataPeerId, cachedData) = entry, dataPeerId == peerId {
if let cachedData = cachedData as? CachedChannelData {
invitedOn = cachedData.invitedOn
switch cachedData.linkedDiscussionPeerId {
case let .known(value):
channelDiscussionGroup = .known(value)
@ -422,7 +428,7 @@ private func extractAssociatedData(
automaticDownloadPeerId = message.peerId
}
return ChatMessageItemAssociatedData(automaticDownloadPeerType: automaticMediaDownloadPeerType, automaticDownloadPeerId: automaticDownloadPeerId, automaticDownloadNetworkType: automaticDownloadNetworkType, preferredStoryHighQuality: preferredStoryHighQuality, isRecentActions: false, subject: subject, contactsPeerIds: contactsPeerIds, channelDiscussionGroup: channelDiscussionGroup, animatedEmojiStickers: animatedEmojiStickers, additionalAnimatedEmojiStickers: additionalAnimatedEmojiStickers, currentlyPlayingMessageId: currentlyPlayingMessageId, isCopyProtectionEnabled: isCopyProtectionEnabled, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: savedMessageTags, defaultReaction: defaultReaction, areStarReactionsEnabled: areStarReactionsEnabled, isPremium: isPremium, accountPeer: accountPeer, alwaysDisplayTranscribeButton: alwaysDisplayTranscribeButton, topicAuthorId: topicAuthorId, hasBots: hasBots, translateToLanguage: translateToLanguage, maxReadStoryId: maxReadStoryId, recommendedChannels: recommendedChannels, audioTranscriptionTrial: audioTranscriptionTrial, chatThemes: chatThemes, deviceContactsNumbers: deviceContactsNumbers, isInline: isInline, showSensitiveContent: showSensitiveContent, isSuspiciousPeer: isSuspiciousPeer, accountCountry: accountCountry)
return ChatMessageItemAssociatedData(automaticDownloadPeerType: automaticMediaDownloadPeerType, automaticDownloadPeerId: automaticDownloadPeerId, automaticDownloadNetworkType: automaticDownloadNetworkType, preferredStoryHighQuality: preferredStoryHighQuality, isRecentActions: false, subject: subject, contactsPeerIds: contactsPeerIds, channelDiscussionGroup: channelDiscussionGroup, animatedEmojiStickers: animatedEmojiStickers, additionalAnimatedEmojiStickers: additionalAnimatedEmojiStickers, currentlyPlayingMessageId: currentlyPlayingMessageId, isCopyProtectionEnabled: isCopyProtectionEnabled, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: savedMessageTags, defaultReaction: defaultReaction, areStarReactionsEnabled: areStarReactionsEnabled, isPremium: isPremium, accountPeer: accountPeer, alwaysDisplayTranscribeButton: alwaysDisplayTranscribeButton, topicAuthorId: topicAuthorId, hasBots: hasBots, translateToLanguage: translateToLanguage, maxReadStoryId: maxReadStoryId, recommendedChannels: recommendedChannels, audioTranscriptionTrial: audioTranscriptionTrial, chatThemes: chatThemes, deviceContactsNumbers: deviceContactsNumbers, isInline: isInline, showSensitiveContent: showSensitiveContent, isSuspiciousPeer: isSuspiciousPeer, accountCountry: accountCountry, isParticipant: isParticipant, invitedOn: invitedOn)
}
private extension ChatHistoryLocationInput {

View file

@ -516,6 +516,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
return
}
#endif*/
self.webView?.bindTrustedOrigin(from: url)
self.webView?.load(URLRequest(url: url))
}
@ -1092,7 +1093,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
guard let controller = self.controller else {
return
}
guard message.frameInfo.isMainFrame else {
guard self.webView?.isTrustedMainFrameMessage(message) == true else {
return
}
guard let body = message.body as? [String: Any] else {

View file

@ -36,11 +36,27 @@ private class WebViewTouchGestureRecognizer: UITapGestureRecognizer {
}
}
private let eventProxySource = "var TelegramWebviewProxyProto = function() {}; " +
"TelegramWebviewProxyProto.prototype.postEvent = function(eventName, eventData) { " +
"window.webkit.messageHandlers.performAction.postMessage({'eventName': eventName, 'eventData': eventData}); " +
"}; " +
"var TelegramWebviewProxy = new TelegramWebviewProxyProto();"
private func jsStringLiteral(_ value: String) -> String {
if let data = try? JSONSerialization.data(withJSONObject: [value], options: []), let string = String(data: data, encoding: .utf8), string.hasPrefix("["), string.hasSuffix("]") {
return String(string.dropFirst().dropLast())
}
return "\"\""
}
private func eventProxySource(trustedOrigin: String) -> String {
return """
(function() {
if (window.location.origin !== \(jsStringLiteral(trustedOrigin))) {
return;
}
var TelegramWebviewProxyProto = function() {};
TelegramWebviewProxyProto.prototype.postEvent = function(eventName, eventData) {
window.webkit.messageHandlers.performAction.postMessage({'eventName': eventName, 'eventData': eventData});
};
window.TelegramWebviewProxy = new TelegramWebviewProxyProto();
})();
"""
}
private let selectionSource = "var css = '*{-webkit-touch-callout:none;} :not(input):not(textarea):not([\"contenteditable\"=\"true\"]){-webkit-user-select:none;}';"
+ " var head = document.head || document.getElementsByTagName('head')[0];"
@ -91,6 +107,7 @@ function tgBrowserDisconnectObserver() {
final class WebAppWebView: WKWebView {
var handleScriptMessage: (WKScriptMessage) -> Void = { _ in }
private(set) var trustedOrigin: String?
var customInsets: UIEdgeInsets = .zero {
didSet {
@ -134,8 +151,6 @@ final class WebAppWebView: WKWebView {
let contentController = WKUserContentController()
var handleScriptMessageImpl: ((WKScriptMessage) -> Void)?
let eventProxyScript = WKUserScript(source: eventProxySource, injectionTime: .atDocumentStart, forMainFrameOnly: true)
contentController.addUserScript(eventProxyScript)
contentController.add(WeakGameScriptMessageHandler { message in
handleScriptMessageImpl?(message)
}, name: "performAction")
@ -187,6 +202,36 @@ final class WebAppWebView: WKWebView {
print()
}
func bindTrustedOrigin(from url: URL) {
guard self.trustedOrigin == nil else {
return
}
guard let origin = normalizedOrigin(url: url) else {
return
}
self.trustedOrigin = origin
let eventProxyScript = WKUserScript(source: eventProxySource(trustedOrigin: origin), injectionTime: .atDocumentStart, forMainFrameOnly: true)
self.configuration.userContentController.addUserScript(eventProxyScript)
}
func isTrustedMainFrameMessage(_ message: WKScriptMessage) -> Bool {
guard message.frameInfo.isMainFrame else {
return false
}
guard let trustedOrigin = self.trustedOrigin else {
return false
}
guard message.frameInfo.securityOriginString == trustedOrigin else {
return false
}
if let currentOrigin = self.origin, currentOrigin != trustedOrigin {
return false
}
return true
}
override func didMoveToSuperview() {
super.didMoveToSuperview()
@ -223,6 +268,9 @@ final class WebAppWebView: WKWebView {
}
func sendEvent(name: String, data: String?) {
guard let trustedOrigin = self.trustedOrigin, self.origin == trustedOrigin else {
return
}
let script = "window.TelegramGameProxy && window.TelegramGameProxy.receiveEvent && window.TelegramGameProxy.receiveEvent(\"\(name)\", \(data ?? "null"))"
self.evaluateJavaScript(script, completionHandler: { _, _ in
})
@ -279,29 +327,39 @@ final class WebAppWebView: WKWebView {
}
var origin: String? {
guard let url = self.url, let scheme = url.scheme, let host = url.host else {
guard let url = self.url else {
return nil
}
let port = url.port
var origin = "\(scheme)://\(host)"
if let port {
origin += ":\(port)"
}
return origin
return normalizedOrigin(url: url)
}
}
extension WKFrameInfo {
var securityOriginString: String {
let securityOrigin = self.securityOrigin
var origin = ""
origin.append(securityOrigin.protocol)
origin.append("://")
origin.append(securityOrigin.host)
if securityOrigin.port != 0 {
origin.append(":")
origin.append("\(securityOrigin.port)")
}
return origin
return normalizedOrigin(scheme: securityOrigin.protocol, host: securityOrigin.host, port: securityOrigin.port == 0 ? nil : securityOrigin.port) ?? ""
}
}
private func normalizedOrigin(url: URL) -> String? {
return normalizedOrigin(scheme: url.scheme, host: url.host, port: url.port)
}
private func normalizedOrigin(scheme: String?, host: String?, port: Int?) -> String? {
guard let scheme = scheme?.lowercased(), !scheme.isEmpty, let host = host?.lowercased(), !host.isEmpty else {
return nil
}
let includePort: Bool
if let port {
includePort = !(scheme == "http" && port == 80) && !(scheme == "https" && port == 443)
} else {
includePort = false
}
if includePort, let port {
return "\(scheme)://\(host):\(port)"
} else {
return "\(scheme)://\(host)"
}
}