2018-09-11 01:30:55 -05:00
|
|
|
// -*- Mode: ObjC; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*-
|
2023-11-18 08:13:14 -06:00
|
|
|
/*
|
|
|
|
* Copyright the Collabora Online contributors.
|
|
|
|
*
|
|
|
|
* SPDX-License-Identifier: MPL-2.0
|
|
|
|
*
|
|
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
*/
|
2018-09-11 01:30:55 -05:00
|
|
|
|
|
|
|
#import "config.h"
|
|
|
|
|
2018-09-13 11:16:00 -05:00
|
|
|
#import <algorithm>
|
|
|
|
|
Call comphelper::LibreOfficeKit::setLanguageTag() to avoid assertion failure
The locale / language tag dance is a bit confusing. Let's hope it now
works sanely.
Without this change, if I as a test in Xcode set the LANG environment
variable when running the app to "de", to match the case where the
user is using their device in a German locale, and then open a
non-trivial .odt document, close it, and open it again, I get an
assertion failure in sw, in SwXPageStyle::GetPropertyValues_Impl().
(Note that the LANG environment variable is not set by the system on
iOS; in this app LANG is just an arbitrary (but familiar) name for an
environment variable used only when debugging in Xcode. Maybe I should
have called it something else to avoid confusing code readers who
might think it has the same meaning as in POSIX systems.)
The above assertion failure does not happen if I don't set LANG. I
don't really fully understand the convoluted mechanisms involved, but
explicitly re-setting the <comphelper/lok.hxx> concept of (global)
language tag each time before opening a document seems to help. Note
that if I set LANG to just "de", the code then later while handling
setView() calls in ChildSession::loadDocument(), sets it to "de-DE"
anyway. But that doesn't seem to harm.
Change-Id: Ic697ed44b4ace488782ebee3aa2e7610bb02e93c
2018-11-12 07:17:24 -06:00
|
|
|
// This is not "external" code in the UNO-based extensions sense. To be able to include
|
|
|
|
// <comphelper/lok.hxx>, we must #define LIBO_INTERNAL_ONLY.
|
|
|
|
|
|
|
|
#define LIBO_INTERNAL_ONLY
|
|
|
|
#include <sal/config.h>
|
2018-11-12 12:13:23 -06:00
|
|
|
#include <sal/log.hxx>
|
Call comphelper::LibreOfficeKit::setLanguageTag() to avoid assertion failure
The locale / language tag dance is a bit confusing. Let's hope it now
works sanely.
Without this change, if I as a test in Xcode set the LANG environment
variable when running the app to "de", to match the case where the
user is using their device in a German locale, and then open a
non-trivial .odt document, close it, and open it again, I get an
assertion failure in sw, in SwXPageStyle::GetPropertyValues_Impl().
(Note that the LANG environment variable is not set by the system on
iOS; in this app LANG is just an arbitrary (but familiar) name for an
environment variable used only when debugging in Xcode. Maybe I should
have called it something else to avoid confusing code readers who
might think it has the same meaning as in POSIX systems.)
The above assertion failure does not happen if I don't set LANG. I
don't really fully understand the convoluted mechanisms involved, but
explicitly re-setting the <comphelper/lok.hxx> concept of (global)
language tag each time before opening a document seems to help. Note
that if I set LANG to just "de", the code then later while handling
setView() calls in ChildSession::loadDocument(), sets it to "de-DE"
anyway. But that doesn't seem to harm.
Change-Id: Ic697ed44b4ace488782ebee3aa2e7610bb02e93c
2018-11-12 07:17:24 -06:00
|
|
|
#include <rtl/ustring.hxx>
|
|
|
|
#include <comphelper/lok.hxx>
|
|
|
|
#include <i18nlangtag/languagetag.hxx>
|
|
|
|
|
2018-09-13 11:16:00 -05:00
|
|
|
#import "ios.h"
|
2018-09-11 01:30:55 -05:00
|
|
|
#import "AppDelegate.h"
|
2020-04-03 16:04:20 -05:00
|
|
|
#import "CODocument.h"
|
2018-09-11 01:30:55 -05:00
|
|
|
#import "DocumentViewController.h"
|
|
|
|
|
2018-09-13 11:16:00 -05:00
|
|
|
#import "ClientSession.hpp"
|
|
|
|
#import "DocumentBroker.hpp"
|
|
|
|
#import "FakeSocket.hpp"
|
2018-09-11 01:30:55 -05:00
|
|
|
#import "Kit.hpp"
|
|
|
|
#import "KitHelper.hpp"
|
|
|
|
#import "Log.hpp"
|
2021-11-18 06:08:14 -06:00
|
|
|
#import "COOLWSD.hpp"
|
2020-04-24 02:46:54 -05:00
|
|
|
#import "MobileApp.hpp"
|
2018-09-11 01:30:55 -05:00
|
|
|
#import "Protocol.hpp"
|
|
|
|
|
2023-03-08 07:01:56 -06:00
|
|
|
static inline bool isMessageOfType(const char *message, const char *type, size_t lengthOfMessage) {
|
|
|
|
// Note: message is not zero terminated but type is
|
|
|
|
size_t typeLen = strlen(type);
|
|
|
|
return (typeLen <= lengthOfMessage && !strncmp(message, type, typeLen));
|
|
|
|
}
|
|
|
|
|
2020-04-03 16:04:20 -05:00
|
|
|
@implementation CODocument
|
2018-09-11 01:30:55 -05:00
|
|
|
|
|
|
|
- (id)contentsForType:(NSString*)typeName error:(NSError **)errorPtr {
|
2020-04-07 13:21:55 -05:00
|
|
|
return [NSData dataWithContentsOfFile:[copyFileURL path] options:0 error:errorPtr];
|
2018-09-11 01:30:55 -05:00
|
|
|
}
|
|
|
|
|
2020-04-24 02:46:54 -05:00
|
|
|
// We keep a running count of opening documents here. This is not necessarily in sync with the
|
|
|
|
// DocBrokerId in DocumentBroker due to potential parallelism when opening multiple documents in
|
|
|
|
// quick succession.
|
|
|
|
|
|
|
|
static std::atomic<unsigned> appDocIdCounter(1);
|
|
|
|
|
2018-09-11 01:30:55 -05:00
|
|
|
- (BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError **)errorPtr {
|
Add a way to have templates specific to a customer deployment
Add two settings: One setting "Template list URL" is a string that
should either be empty (the typical case for a random user of the
app), or contain a https: URL. If this setting is empty, only the
templates bundled in the app are provided.
If the "Template list URL" is non-empty, it should be a https: URL
pointing to a text file (or dynamically generated text resource). That
file is downloaded and read when the app starts. Each line in the file
should either be a comment (starting with a hash '#'), or a https: URL
pointing to a template document, that is of type .ott, .ots, or .otp.
That document is downloaded if it hasn't been downloaded already, or
if its time stamp is newer than that of the already downloaded copy.
Also a thumbnail image for the template, formed by appending ".png" to
its URL, is downloaded, if available.
Any previously downloaded templates that aren't mentioned in the list
file are removed.
The intent is that in some managed mass deployment environment, the
mobile device management software would set up this setting, so that
the end-user devices would see the same templates.
Obviously, this URL does not have to point to a static file on a web
server, but could point to some dynamically generated resource on a
web server, that enumerates the templates available on the server and
returns their URLs as a text document.
Another setting is "Empty tile cache next time". This is a toggle. If
toggled on, the next time a document is opened in the app, the tile
cache is emptied (and the toggle is reset off). This is mostly for
potential problem solving, and might be removd later.
Various refactoring to support the new functionality.
Change-Id: Ie2ebf032acb9e43bb1c6f7ae4d0c449ae66eaa05
2018-12-17 14:18:49 -06:00
|
|
|
|
2020-04-03 16:04:20 -05:00
|
|
|
// If this method is called a second time on the same CODocument object, just ignore it. This
|
2020-04-26 15:55:16 -05:00
|
|
|
// seems to happen occasionally when the device is awakened after sleep. See tdf#122543.
|
2019-03-20 08:08:42 -05:00
|
|
|
if (fakeClientFd >= 0)
|
|
|
|
return YES;
|
|
|
|
|
2018-09-13 11:16:00 -05:00
|
|
|
fakeClientFd = fakeSocketSocket();
|
2020-04-07 13:21:55 -05:00
|
|
|
|
2020-07-07 08:13:25 -05:00
|
|
|
appDocId = appDocIdCounter++;
|
|
|
|
NSURL *copyFileDirectory = [[NSFileManager.defaultManager temporaryDirectory] URLByAppendingPathComponent:[NSString stringWithFormat:@"%d", appDocId]];
|
|
|
|
if (![NSFileManager.defaultManager createDirectoryAtURL:copyFileDirectory withIntermediateDirectories:YES attributes:nil error:nil]) {
|
|
|
|
LOG_ERR("Could not create directory " << [[copyFileDirectory path] UTF8String]);
|
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
|
|
|
|
copyFileURL = [copyFileDirectory URLByAppendingPathComponent:[[[self fileURL] path] lastPathComponent]];
|
2020-04-07 13:21:55 -05:00
|
|
|
|
|
|
|
NSError *error;
|
|
|
|
[[NSFileManager defaultManager] removeItemAtURL:copyFileURL error:nil];
|
|
|
|
[[NSFileManager defaultManager] copyItemAtURL:[self fileURL] toURL:copyFileURL error:&error];
|
|
|
|
if (error != nil)
|
|
|
|
return NO;
|
2018-11-09 15:33:55 -06:00
|
|
|
|
2021-10-26 08:23:28 -05:00
|
|
|
NSURL *url = [[NSBundle mainBundle] URLForResource:@"cool" withExtension:@"html"];
|
2018-09-13 11:16:00 -05:00
|
|
|
NSURLComponents *components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO];
|
2021-09-01 03:11:55 -05:00
|
|
|
DocumentData::allocate(appDocId).coDocument = self;
|
2020-04-07 13:21:55 -05:00
|
|
|
components.queryItems = @[ [NSURLQueryItem queryItemWithName:@"file_path" value:[copyFileURL absoluteString]],
|
2018-10-12 09:48:45 -05:00
|
|
|
[NSURLQueryItem queryItemWithName:@"closebutton" value:@"1"],
|
2022-04-05 06:27:19 -05:00
|
|
|
[NSURLQueryItem queryItemWithName:@"permission" value:(readOnly ? @"readonly" : @"edit")],
|
2020-04-24 02:46:54 -05:00
|
|
|
[NSURLQueryItem queryItemWithName:@"lang" value:app_locale],
|
|
|
|
[NSURLQueryItem queryItemWithName:@"appdocid" value:[NSString stringWithFormat:@"%u", appDocId]],
|
2020-10-20 07:00:03 -05:00
|
|
|
[NSURLQueryItem queryItemWithName:@"userinterfacemode" value:([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad ? @"notebookbar" : @"classic")],
|
2023-03-11 16:40:07 -06:00
|
|
|
// Related to issue #5841: the iOS app sets the
|
|
|
|
// base text direction via the "dir" parameter
|
|
|
|
[NSURLQueryItem queryItemWithName:@"dir" value:app_text_direction],
|
2018-11-09 15:33:55 -06:00
|
|
|
];
|
|
|
|
|
2018-09-13 11:16:00 -05:00
|
|
|
NSURLRequest *request = [[NSURLRequest alloc]initWithURL:components.URL];
|
|
|
|
[self.viewController.webView loadRequest:request];
|
2018-09-11 01:30:55 -05:00
|
|
|
|
|
|
|
return YES;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)send2JS:(const char *)buffer length:(int)length {
|
2021-11-18 06:08:14 -06:00
|
|
|
LOG_TRC("To JS: " << COOLProtocol::getAbbreviatedMessage(buffer, length).c_str());
|
2018-09-11 01:30:55 -05:00
|
|
|
|
2019-03-22 08:49:47 -05:00
|
|
|
const unsigned char *ubufp = (const unsigned char *)buffer;
|
|
|
|
std::vector<char> data;
|
2023-05-15 13:25:23 -05:00
|
|
|
// Reserve the maxiumum possible length after encoding
|
|
|
|
// This avoids an excessive number of reallocations. This is overkill
|
|
|
|
// for non-binary messages, but most non-binary messages appear to be
|
|
|
|
// under 1K bytes in length. In contrast, it appears that binary
|
|
|
|
// messags routinely use at least 75% of the maximum possible length.
|
|
|
|
data.reserve((length * 4) + 1);
|
2023-03-05 19:00:05 -06:00
|
|
|
bool newlineFound = false;
|
2023-03-08 07:01:56 -06:00
|
|
|
bool binaryMessage = (isMessageOfType(buffer, "tile:", length) ||
|
|
|
|
isMessageOfType(buffer, "tilecombine:", length) ||
|
|
|
|
isMessageOfType(buffer, "delta:", length) ||
|
|
|
|
isMessageOfType(buffer, "renderfont:", length) ||
|
|
|
|
isMessageOfType(buffer, "rendersearchlist:", length) ||
|
|
|
|
isMessageOfType(buffer, "windowpaint:", length));
|
2023-05-15 13:22:38 -05:00
|
|
|
|
|
|
|
const char *pretext = "window.TheFakeWebSocket.onmessage({'data': '";
|
|
|
|
const int pretextlen = strlen(pretext);
|
|
|
|
for (int i = 0; i < pretextlen; i++)
|
|
|
|
data.push_back(pretext[i]);
|
|
|
|
|
2019-03-22 08:49:47 -05:00
|
|
|
for (int i = 0; i < length; i++) {
|
2023-03-08 07:01:56 -06:00
|
|
|
// Another fix for issue #5843 limit non-ASCII escaping to only
|
|
|
|
// certain message types
|
|
|
|
if (binaryMessage && !newlineFound && ubufp[i] == '\n')
|
2023-03-05 19:00:05 -06:00
|
|
|
newlineFound = true;
|
|
|
|
|
|
|
|
// Fix issue #5843 escape non-ASCII characters only for image data
|
|
|
|
// Passing non-ASCII, UTF-8 text from native to JavaScript works
|
|
|
|
// fine, but images become corrupted if any non-ASCII bytes are
|
|
|
|
// not escaped.
|
|
|
|
// The Socket._extractTextImg() JavaScript function assumes that,
|
|
|
|
// in the iOS app, the first newline separates text from image data
|
|
|
|
// so assume all bytes after the first new line are image data.
|
|
|
|
if (ubufp[i] < ' ' || ubufp[i] == '\'' || ubufp[i] == '\\' || (newlineFound && ubufp[i] >= 0x80)) {
|
2019-03-22 08:49:47 -05:00
|
|
|
data.push_back('\\');
|
|
|
|
data.push_back('x');
|
|
|
|
data.push_back("0123456789abcdef"[(ubufp[i] >> 4) & 0x0F]);
|
|
|
|
data.push_back("0123456789abcdef"[ubufp[i] & 0x0F]);
|
|
|
|
} else {
|
|
|
|
data.push_back(ubufp[i]);
|
2018-09-13 11:16:00 -05:00
|
|
|
}
|
2018-09-11 01:30:55 -05:00
|
|
|
}
|
2023-05-15 13:22:38 -05:00
|
|
|
|
|
|
|
const char *posttext = "'});";
|
|
|
|
const int posttextlen = strlen(posttext);
|
|
|
|
for (int i = 0; i < posttextlen; i++)
|
|
|
|
data.push_back(posttext[i]);
|
|
|
|
|
2019-03-22 08:49:47 -05:00
|
|
|
data.push_back(0);
|
|
|
|
|
2023-05-15 13:43:07 -05:00
|
|
|
// Related to issue #5876: don't autorelease large NSStrings
|
|
|
|
// The +[NSString string...] selectors won't be released until
|
|
|
|
// an enclosing autorelease pool is released. But since we use
|
|
|
|
// ARC, we don't know where the compiler has inserted the
|
|
|
|
// autorelease pool so JS messages may not be released until
|
|
|
|
// after a very long time potentially causing an out of memory
|
|
|
|
// crash. So, use the -[[NSString alloc] init...] selectors
|
|
|
|
// instead.
|
|
|
|
NSString *js = [[NSString alloc] initWithUTF8String:data.data()];
|
2023-05-15 13:22:38 -05:00
|
|
|
if (!js) {
|
2023-03-08 07:01:56 -06:00
|
|
|
char outBuf[length + 1];
|
|
|
|
memcpy(outBuf, buffer, length);
|
|
|
|
outBuf[length] = '\0';
|
|
|
|
LOG_ERR("Couldn't create NSString with message: " << outBuf);
|
|
|
|
return;
|
|
|
|
}
|
2019-03-22 08:49:47 -05:00
|
|
|
|
2019-03-22 09:37:32 -05:00
|
|
|
NSString *subjs = [js substringToIndex:std::min(100ul, js.length)];
|
|
|
|
if (subjs.length < js.length)
|
|
|
|
subjs = [subjs stringByAppendingString:@"..."];
|
|
|
|
|
|
|
|
// LOG_TRC("Evaluating JavaScript: " << [subjs UTF8String]);
|
2019-03-22 08:49:47 -05:00
|
|
|
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
|
|
[self.viewController.webView evaluateJavaScript:js
|
|
|
|
completionHandler:^(id _Nullable obj, NSError * _Nullable error)
|
|
|
|
{
|
|
|
|
if (error) {
|
2019-03-22 09:37:32 -05:00
|
|
|
LOG_ERR("Error after " << [subjs UTF8String] << ": " << [[error localizedDescription] UTF8String]);
|
2019-03-22 08:49:47 -05:00
|
|
|
NSString *jsException = error.userInfo[@"WKJavaScriptExceptionMessage"];
|
|
|
|
if (jsException != nil)
|
|
|
|
LOG_ERR("JavaScript exception: " << [jsException UTF8String]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
];
|
|
|
|
});
|
2018-09-11 01:30:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
// vim:set shiftwidth=4 softtabstop=4 expandtab:
|