UtilsFramework.m 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646
  1. //
  2. // UtilsFramework.m
  3. // Owncloud iOs Client
  4. //
  5. // Copyright (C) 2016, ownCloud GmbH. ( http://www.owncloud.org/ )
  6. //
  7. // Permission is hereby granted, free of charge, to any person obtaining a copy
  8. // of this software and associated documentation files (the "Software"), to deal
  9. // in the Software without restriction, including without limitation the rights
  10. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  11. // copies of the Software, and to permit persons to whom the Software is
  12. // furnished to do so, subject to the following conditions:
  13. // The above copyright notice and this permission notice shall be included in
  14. // all copies or substantial portions of the Software.
  15. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  20. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  21. // THE SOFTWARE.
  22. //
  23. #import "UtilsFramework.h"
  24. #import "OCCommunication.h"
  25. #import "OCFrameworkConstants.h"
  26. #import "OCErrorMsg.h"
  27. #import "OCConstants.h"
  28. #define kSAMLFragmentArray [NSArray arrayWithObjects: @"wayf", @"saml", @"sso_orig_uri", nil]
  29. @implementation UtilsFramework
  30. /*
  31. * Method that return a unique Id.
  32. * The global ID for the process includes the host name, process ID, and a time stamp,
  33. * which ensures that the ID is unique for the network
  34. * @return -> Unique Id (token)
  35. */
  36. + (NSString *) getUserSessionToken{
  37. return [[NSProcessInfo processInfo] globallyUniqueString];
  38. }
  39. /*
  40. * Method that check the file name or folder name to find forbidden characters
  41. * This is the forbidden characters in server: "\", "/","<",">",":",""","|","?","*"
  42. * @fileName -> file name
  43. *
  44. * @isFCSupported -> From ownCloud 8.1 the forbidden characters are controller by the server except the '/'
  45. */
  46. + (BOOL) isForbiddenCharactersInFileName:(NSString*)fileName withForbiddenCharactersSupported:(BOOL)isFCSupported{
  47. BOOL thereAreForbidenCharacters = NO;
  48. //Check the filename
  49. for(NSInteger i = 0 ;i<[fileName length]; i++) {
  50. if ([fileName characterAtIndex:i]=='/'){
  51. thereAreForbidenCharacters = YES;
  52. }
  53. if (!isFCSupported) {
  54. if ([fileName characterAtIndex:i] == '\\'){
  55. thereAreForbidenCharacters = YES;
  56. }
  57. if ([fileName characterAtIndex:i] == '<'){
  58. thereAreForbidenCharacters = YES;
  59. }
  60. if ([fileName characterAtIndex:i] == '>'){
  61. thereAreForbidenCharacters = YES;
  62. }
  63. if ([fileName characterAtIndex:i] == '"'){
  64. thereAreForbidenCharacters = YES;
  65. }
  66. if ([fileName characterAtIndex:i] == ','){
  67. thereAreForbidenCharacters = YES;
  68. }
  69. if ([fileName characterAtIndex:i] == ':'){
  70. thereAreForbidenCharacters = YES;
  71. }
  72. if ([fileName characterAtIndex:i] == '|'){
  73. thereAreForbidenCharacters = YES;
  74. }
  75. if ([fileName characterAtIndex:i] == '?'){
  76. thereAreForbidenCharacters = YES;
  77. }
  78. if ([fileName characterAtIndex:i] == '*'){
  79. thereAreForbidenCharacters = YES;
  80. }
  81. }
  82. }
  83. return thereAreForbidenCharacters;
  84. }
  85. + (NSError *) getErrorWithCode:(NSInteger)errorCode andCustomMessageFromTheServer:(NSString *)message {
  86. NSError *error = nil;
  87. NSMutableDictionary* details = [NSMutableDictionary dictionary];
  88. [details setValue:message forKey:NSLocalizedDescriptionKey];
  89. error = [NSError errorWithDomain:k_domain_error_code code:errorCode userInfo:details];
  90. return error;
  91. }
  92. /*
  93. * Get error for the same errors in the share api
  94. *
  95. * Statuscodes:
  96. * 100 - successful
  97. * 400 - wrong or no update parameter given
  98. * 403 - public upload disabled by the admin (or is neccesary put a password)
  99. * 404 - couldn’t update share
  100. *
  101. */
  102. + (NSError *) getShareAPIErrorByCode:(NSInteger)errorCode {
  103. NSError *error = nil;
  104. switch (errorCode) {
  105. case kOCErrorSharedAPIWrong:
  106. {
  107. NSMutableDictionary* details = [NSMutableDictionary dictionary];
  108. [details setValue:@"Wrong or no update parameter given" forKey:NSLocalizedDescriptionKey];
  109. error = [NSError errorWithDomain:k_domain_error_code code:kOCErrorSharedAPIWrong userInfo:details];
  110. break;
  111. }
  112. case kOCErrorServerForbidden:
  113. {
  114. NSMutableDictionary* details = [NSMutableDictionary dictionary];
  115. [details setValue:@"Public upload disabled by the admin" forKey:NSLocalizedDescriptionKey];
  116. error = [NSError errorWithDomain:k_domain_error_code code:kOCErrorServerForbidden userInfo:details];
  117. break;
  118. }
  119. case kOCErrorServerPathNotFound:
  120. {
  121. NSMutableDictionary* details = [NSMutableDictionary dictionary];
  122. [details setValue:@"Couldn't update share" forKey:NSLocalizedDescriptionKey];
  123. error = [NSError errorWithDomain:k_domain_error_code code:kOCErrorServerPathNotFound userInfo:details];
  124. break;
  125. }
  126. default:
  127. {
  128. NSMutableDictionary* details = [NSMutableDictionary dictionary];
  129. [details setValue:@"Unknow error" forKey:NSLocalizedDescriptionKey];
  130. error = [NSError errorWithDomain:k_domain_error_code code:OCErrorUnknow userInfo:details];
  131. break;
  132. }
  133. }
  134. return error;
  135. }
  136. ///-----------------------------------
  137. /// @name getErrorByCodeId
  138. ///-----------------------------------
  139. /**
  140. * Method to return a Error with description from a OC Error code
  141. *
  142. * @param int -> errorCode number to identify the OC Error
  143. *
  144. * @return NSError
  145. *
  146. */
  147. + (NSError *) getErrorByCodeId:(int) errorCode {
  148. NSError *error = nil;
  149. switch (errorCode) {
  150. case OCErrorForbidenCharacters:
  151. {
  152. NSMutableDictionary* details = [NSMutableDictionary dictionary];
  153. [details setValue:@"You have entered forbbiden characters" forKey:NSLocalizedDescriptionKey];
  154. error = [NSError errorWithDomain:k_domain_error_code code:OCErrorForbidenCharacters userInfo:details];
  155. break;
  156. }
  157. case OCErrorMovingDestinyNameHaveForbiddenCharacters:
  158. {
  159. NSMutableDictionary* details = [NSMutableDictionary dictionary];
  160. [details setValue:@"The file or folder that you are moving have forbidden characters" forKey:NSLocalizedDescriptionKey];
  161. error = [NSError errorWithDomain:k_domain_error_code code:OCErrorMovingDestinyNameHaveForbiddenCharacters userInfo:details];
  162. break;
  163. }
  164. case OCErrorMovingTheDestinyAndOriginAreTheSame:
  165. {
  166. NSMutableDictionary* details = [NSMutableDictionary dictionary];
  167. [details setValue:@"You are trying to move the file to the same folder" forKey:NSLocalizedDescriptionKey];
  168. error = [NSError errorWithDomain:k_domain_error_code code:OCErrorMovingTheDestinyAndOriginAreTheSame userInfo:details];
  169. break;
  170. }
  171. case OCErrorMovingFolderInsideHimself:
  172. {
  173. NSMutableDictionary* details = [NSMutableDictionary dictionary];
  174. [details setValue:@"You are trying to move a folder inside himself" forKey:NSLocalizedDescriptionKey];
  175. error = [NSError errorWithDomain:k_domain_error_code code:OCErrorMovingFolderInsideHimself userInfo:details];
  176. break;
  177. }
  178. case kOCErrorServerPathNotFound:
  179. {
  180. NSMutableDictionary* details = [NSMutableDictionary dictionary];
  181. [details setValue:@"You are trying to access to a file that does not exist" forKey:NSLocalizedDescriptionKey];
  182. error = [NSError errorWithDomain:k_domain_error_code code:kOCErrorServerPathNotFound userInfo:details];
  183. break;
  184. }
  185. case kOCErrorServerForbidden:
  186. {
  187. NSMutableDictionary* details = [NSMutableDictionary dictionary];
  188. [details setValue:@"You are trying to do a forbbiden operation" forKey:NSLocalizedDescriptionKey];
  189. error = [NSError errorWithDomain:k_domain_error_code code:kOCErrorServerForbidden userInfo:details];
  190. break;
  191. }
  192. case OCServerErrorForbiddenCharacters:
  193. {
  194. NSMutableDictionary* details = [NSMutableDictionary dictionary];
  195. [details setValue:@"Server said: File name contains at least one invalid character" forKey:NSLocalizedDescriptionKey];
  196. error = [NSError errorWithDomain:k_domain_error_code code:OCServerErrorForbiddenCharacters userInfo:details];
  197. break;
  198. }
  199. default:
  200. {
  201. NSMutableDictionary* details = [NSMutableDictionary dictionary];
  202. [details setValue:@"Unknow error" forKey:NSLocalizedDescriptionKey];
  203. error = [NSError errorWithDomain:k_domain_error_code code:OCErrorUnknow userInfo:details];
  204. break;
  205. }
  206. }
  207. return error;
  208. }
  209. ///-----------------------------------
  210. /// @name getFileNameOrFolderByPath
  211. ///-----------------------------------
  212. /**
  213. * Method that return a filename from a path
  214. *
  215. * @param NSString -> path of the file (including the file)
  216. *
  217. * @return NSString -> fileName
  218. *
  219. */
  220. + (NSString *) getFileNameOrFolderByPath:(NSString *) path {
  221. NSString *output;
  222. if (path && [path length] > 0) {
  223. NSArray *listItems = [path componentsSeparatedByString:@"/"];
  224. output = [listItems objectAtIndex:[listItems count]-1];
  225. if ([output length] <= 0) {
  226. output = [listItems objectAtIndex:[listItems count]-2];
  227. }
  228. //If is a folder we set the last character in order to compare folders with folders and files with files
  229. /*if([path hasSuffix:@"/"]) {
  230. output = [NSString stringWithFormat:@"%@/", output];
  231. }*/
  232. }
  233. return output;
  234. }
  235. /*
  236. * Method that return a boolean that indicate if is the same url
  237. */
  238. + (BOOL) isTheSameFileOrFolderByNewURLString:(NSString *) newURLString andOriginURLString:(NSString *) originalURLString{
  239. if ([originalURLString isEqualToString:newURLString]) {
  240. return YES;
  241. }
  242. return NO;
  243. }
  244. /*
  245. * Method that return a boolean that indicate if newUrl is under the original Url
  246. */
  247. + (BOOL) isAFolderUnderItByNewURLString:(NSString *) newURLString andOriginURLString:(NSString *) originalURLString{
  248. if([originalURLString length] < [newURLString length]) {
  249. NSString *subString = [newURLString substringToIndex: [originalURLString length]];
  250. if([originalURLString isEqualToString: subString]){
  251. newURLString = [newURLString substringFromIndex:[subString length]];
  252. if ([newURLString rangeOfString:@"/"].location == NSNotFound) {
  253. //Is a rename of the last part of the file or folder
  254. return NO;
  255. } else {
  256. //Is a move inside himself
  257. return YES;
  258. }
  259. }
  260. }
  261. return NO;
  262. }
  263. ///-----------------------------------
  264. /// @name getSizeInBytesByPath
  265. ///-----------------------------------
  266. /**
  267. * Method to return the size of a file by a path
  268. *
  269. * @param NSString -> path of the file
  270. *
  271. * @return long long -> size of the file in the path
  272. */
  273. + (long long) getSizeInBytesByPath:(NSString *)path {
  274. long long fileLength = [[[[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil] valueForKey:NSFileSize] unsignedLongLongValue];
  275. return fileLength;
  276. }
  277. ///-----------------------------------
  278. /// @name isURLWithSamlFragment:
  279. ///-----------------------------------
  280. /**
  281. * Method to check a url string to looking for a SAML fragment
  282. *
  283. * @param urlString -> url from redirect server
  284. *
  285. * @return BOOL -> the result about if exist the SAML fragment or not
  286. */
  287. + (BOOL) isURLWithSamlFragment:(NSString*)urlString {
  288. urlString = [urlString lowercaseString];
  289. if (urlString) {
  290. for (NSString* samlFragment in kSAMLFragmentArray) {
  291. if ([urlString rangeOfString:samlFragment options:NSCaseInsensitiveSearch].location != NSNotFound) {
  292. NSLog(@"A SAML fragment is in the request url");
  293. return YES;
  294. }
  295. }
  296. }
  297. return NO;
  298. }
  299. + (NSString *) AFBase64EncodedStringFromString:(NSString *) string {
  300. NSData *data = [NSData dataWithBytes:[string UTF8String] length:[string lengthOfBytesUsingEncoding:NSUTF8StringEncoding]];
  301. NSUInteger length = [data length];
  302. NSMutableData *mutableData = [NSMutableData dataWithLength:((length + 2) / 3) * 4];
  303. uint8_t *input = (uint8_t *)[data bytes];
  304. uint8_t *output = (uint8_t *)[mutableData mutableBytes];
  305. for (NSUInteger i = 0; i < length; i += 3) {
  306. NSUInteger value = 0;
  307. for (NSUInteger j = i; j < (i + 3); j++) {
  308. value <<= 8;
  309. if (j < length) {
  310. value |= (0xFF & input[j]);
  311. }
  312. }
  313. static uint8_t const kAFBase64EncodingTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  314. NSUInteger idx = (i / 3) * 4;
  315. output[idx + 0] = kAFBase64EncodingTable[(value >> 18) & 0x3F];
  316. output[idx + 1] = kAFBase64EncodingTable[(value >> 12) & 0x3F];
  317. output[idx + 2] = (i + 1) < length ? kAFBase64EncodingTable[(value >> 6) & 0x3F] : '=';
  318. output[idx + 3] = (i + 2) < length ? kAFBase64EncodingTable[(value >> 0) & 0x3F] : '=';
  319. }
  320. return [[NSString alloc] initWithData:mutableData encoding:NSASCIIStringEncoding];
  321. }
  322. #pragma mark - Manage Cookies
  323. //-----------------------------------
  324. /// @name addCookiesToStorageFromResponse
  325. ///-----------------------------------
  326. /**
  327. * Method to storage all the cookies from a response in order to use them in future requests
  328. *
  329. * @param NSHTTPURLResponse -> response
  330. * @param NSURL -> url
  331. *
  332. */
  333. + (void) addCookiesToStorageFromResponse: (NSURLResponse *) response andPath:(NSURL *) url {
  334. //TODO: Using NSURLSession this should not be necessary
  335. /*NSArray* cookies = [NSHTTPCookie cookiesWithResponseHeaderFields:[response allHeaderFields] forURL:url];
  336. for (NSHTTPCookie *current in cookies) {
  337. [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:current];
  338. }*/
  339. }
  340. //-----------------------------------
  341. /// @name getRequestWithCookiesByRequest
  342. ///-----------------------------------
  343. /**
  344. * Method to return a request with all the necessary cookies of the original url without redirection
  345. *
  346. * @param NSMutableURLRequest -> request
  347. * @param NSString -> originalUrlServer
  348. *
  349. * @return request
  350. *
  351. */
  352. + (NSMutableURLRequest *) getRequestWithCookiesByRequest: (NSMutableURLRequest *) request andOriginalUrlServer:(NSString *) originalUrlServer {
  353. //We add the cookies of that URL
  354. NSArray *cookieStorage = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:[NSURL URLWithString:originalUrlServer]];
  355. NSDictionary *cookieHeaders = [NSHTTPCookie requestHeaderFieldsWithCookies:cookieStorage];
  356. for (NSString *key in cookieHeaders) {
  357. [request addValue:cookieHeaders[key] forHTTPHeaderField:key];
  358. }
  359. return request;
  360. }
  361. //-----------------------------------
  362. /// @name deleteAllCookies
  363. ///-----------------------------------
  364. /**
  365. * Method to clean the CookiesStorage
  366. *
  367. */
  368. + (void) deleteAllCookies {
  369. NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
  370. for (NSHTTPCookie *each in cookieStorage.cookies) {
  371. [cookieStorage deleteCookie:each];
  372. }
  373. }
  374. //-----------------------------------
  375. /// @name isServerVersionHigherThanLimitVersion
  376. ///-----------------------------------
  377. /**
  378. * Method to detect if a server version is higher than a limit version.
  379. * This methos is used for example to know if the server have share API or support Cookies
  380. *
  381. * @param NSString -> serverVersion
  382. * @param NSArray -> limitVersion
  383. *
  384. * @return BOOL
  385. *
  386. */
  387. + (BOOL) isServerVersion:(NSString *) serverVersionString higherThanLimitVersion:(NSArray *) limitVersion {
  388. //Split the strings - Type 5.0.13
  389. NSArray *spliteVersion = [serverVersionString componentsSeparatedByString:@"."];
  390. NSMutableArray *serverVersion = [NSMutableArray new];
  391. for (NSString *string in spliteVersion) {
  392. [serverVersion addObject:string];
  393. }
  394. __block BOOL isSupported = NO;
  395. //Loop of compare
  396. [limitVersion enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
  397. NSString *firstVersionString = obj;
  398. NSString *currentVersionString;
  399. if ([serverVersion count] > idx) {
  400. currentVersionString = [serverVersion objectAtIndex:idx];
  401. int firstVersionInt = [firstVersionString intValue];
  402. int currentVersionInt = [currentVersionString intValue];
  403. //NSLog(@"firstVersion item %d item is: %d", idx, firstVersionInt);
  404. //NSLog(@"currentVersion item %d item is: %d", idx, currentVersionInt);
  405. //Comparation secure
  406. switch (idx) {
  407. case 0:
  408. //if the first number is higher
  409. if (currentVersionInt > firstVersionInt) {
  410. isSupported = YES;
  411. *stop=YES;
  412. }
  413. //if the first number is lower
  414. if (currentVersionInt < firstVersionInt) {
  415. isSupported = NO;
  416. *stop=YES;
  417. }
  418. break;
  419. case 1:
  420. //if the seccond number is higger
  421. if (currentVersionInt > firstVersionInt) {
  422. isSupported = YES;
  423. *stop=YES;
  424. }
  425. //if the second number is lower
  426. if (currentVersionInt < firstVersionInt) {
  427. isSupported = NO;
  428. *stop=YES;
  429. }
  430. break;
  431. case 2:
  432. //if the third number is higger or equal
  433. if (currentVersionInt >= firstVersionInt) {
  434. isSupported = YES;
  435. *stop=YES;
  436. } else {
  437. //if the third number is lower
  438. isSupported = NO;
  439. *stop=YES;
  440. }
  441. break;
  442. default:
  443. break;
  444. }
  445. } else {
  446. isSupported = NO;
  447. *stop=YES;
  448. }
  449. }];
  450. return isSupported;
  451. }
  452. #pragma mark - Share Permissions
  453. + (NSInteger) getPermissionsValueByCanEdit:(BOOL)canEdit andCanCreate:(BOOL)canCreate andCanChange:(BOOL)canChange andCanDelete:(BOOL)canDelete andCanShare:(BOOL)canShare andIsFolder:(BOOL) isFolder {
  454. NSInteger permissionsValue = k_read_share_permission;
  455. if (canEdit && !isFolder) {
  456. permissionsValue = permissionsValue + k_update_share_permission;
  457. }
  458. if (canCreate & isFolder) {
  459. permissionsValue = permissionsValue + k_create_share_permission;
  460. }
  461. if (canChange && isFolder) {
  462. permissionsValue = permissionsValue + k_update_share_permission;
  463. }
  464. if (canDelete & isFolder) {
  465. permissionsValue = permissionsValue + k_delete_share_permission;
  466. }
  467. if (canShare) {
  468. permissionsValue = permissionsValue + k_share_share_permission;
  469. }
  470. return permissionsValue;
  471. }
  472. + (BOOL) isPermissionToCanCreate:(NSInteger) permissionValue {
  473. BOOL canCreate = ((permissionValue & k_create_share_permission) > 0);
  474. return canCreate;
  475. }
  476. + (BOOL) isPermissionToCanChange:(NSInteger) permissionValue {
  477. BOOL canChange = ((permissionValue & k_update_share_permission) > 0);
  478. return canChange;
  479. }
  480. + (BOOL) isPermissionToCanDelete:(NSInteger) permissionValue {
  481. BOOL canDelete = ((permissionValue & k_delete_share_permission) > 0);
  482. return canDelete;
  483. }
  484. + (BOOL) isPermissionToCanShare:(NSInteger) permissionValue {
  485. BOOL canShare = ((permissionValue & k_share_share_permission) > 0);
  486. return canShare;
  487. }
  488. + (BOOL) isAnyPermissionToEdit:(NSInteger) permissionValue {
  489. BOOL canCreate = [self isPermissionToCanCreate:permissionValue];
  490. BOOL canChange = [self isPermissionToCanChange:permissionValue];
  491. BOOL canDelete = [self isPermissionToCanDelete:permissionValue];
  492. BOOL canEdit = (canCreate || canChange || canDelete);
  493. return canEdit;
  494. }
  495. + (BOOL) isPermissionToRead:(NSInteger) permissionValue {
  496. BOOL canRead = ((permissionValue & k_read_share_permission) > 0);
  497. return canRead;
  498. }
  499. + (BOOL) isPermissionToReadCreateUpdate:(NSInteger) permissionValue {
  500. BOOL canRead = [self isPermissionToRead:permissionValue];
  501. BOOL canCreate = [self isPermissionToCanCreate:permissionValue];
  502. BOOL canChange = [self isPermissionToCanChange:permissionValue];
  503. BOOL canEdit = (canCreate && canChange && canRead);
  504. return canEdit;
  505. }
  506. @end