ShaarliCmdUpdateEndpoint.m 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. //
  2. // ShaarliCmdUpdateEndpoint.m
  3. // ShaarliOS
  4. //
  5. // Created by Marcus Rohrmoser on 20.01.16.
  6. // Copyright (c) 2016 Marcus Rohrmoser. All rights reserved.
  7. //
  8. #import "ShaarliCmdUpdateEndpoint.h"
  9. #define CMD_DO_LOGOUT @"?do=logout"
  10. #define CMD_DO_LOGIN @"?do=login"
  11. #define CMD_DO_CHANGEPASSWD @"?do=changepasswd"
  12. typedef enum : NSUInteger {
  13. Start,
  14. GetLoginFormAndToken,
  15. DoLogout,
  16. // HttpAuth,
  17. PostLoginForm,
  18. Error,
  19. Success,
  20. Done
  21. }
  22. State_t;
  23. @interface ShaarliCmdUpdateEndpoint() {
  24. State_t state;
  25. }
  26. @property (nonatomic, copy) void (^blockCompletion)(ShaarliCmdUpdateEndpoint * me, NSError * error);
  27. @property (readwrite, nonatomic, strong) NSError *error;
  28. @property (readonly, nonatomic, assign) BOOL authMissing;
  29. @property (nonatomic, strong) NSString *title;
  30. @property (nonatomic, strong) NSString *scheme;
  31. @property (nonatomic, strong) NSURLCredential *credential;
  32. @property (nonatomic, strong) NSMutableDictionary *formDict;
  33. @property (nonatomic, strong) NSString *endpoint;
  34. @property (nonatomic, assign) BOOL privateDefault;
  35. @property (nonatomic, assign) BOOL tagsActive;
  36. @property (nonatomic, strong) NSString *tagsDefault;
  37. @end
  38. @implementation ShaarliCmdUpdateEndpoint
  39. -(instancetype)initWithEndpoint:(NSString *)endpoint user:(NSString *)user pass:(NSString *)pass privateDefault:(BOOL)privateDefault
  40. tagsActive:(BOOL)tagsA tagsDefault:(NSString *)tagsD completion:( void (^)(ShaarliCmdUpdateEndpoint * me, NSError * error) )completion
  41. {
  42. if( self = [super init] ) {
  43. self.blockCompletion = completion;
  44. self.form = @"loginform";
  45. self.endpoint = endpoint;
  46. self.privateDefault = privateDefault;
  47. self.tagsActive = tagsA;
  48. self.tagsDefault = tagsD;
  49. if( [self.endpoint hasPrefix:HTTP_HTTPS @"://"] )
  50. self.endpoint = [self.endpoint substringFromIndex:[HTTP_HTTPS @"://" length]];
  51. if( [self.endpoint hasPrefix:HTTP_HTTP @"://"] )
  52. self.endpoint = [self.endpoint substringFromIndex:[HTTP_HTTP @"://" length]];
  53. self.scheme = HTTP_HTTPS;
  54. // check for credential in store
  55. // MRLogD (@"credentials in storage: %@", session.configuration.URLCredentialStorage.allCredentials, nil);
  56. self.credential = [NSURLCredential credentialWithUser:user password:pass persistence:NSURLCredentialPersistenceSynchronizable];
  57. NSParameterAssert(self.credential.user && [self.credential.user isEqualToString:user]);
  58. NSParameterAssert(self.credential.password && [self.credential.password isEqualToString:pass]);
  59. state = Start;
  60. }
  61. return self;
  62. }
  63. -(BOOL)authMissing
  64. {
  65. return NO;
  66. }
  67. -(NSURL *)endpointURL
  68. {
  69. NSString *u = [NSString stringWithFormat:@"%@" @"://" @"%@", self.scheme, self.endpoint, nil];
  70. return [[NSURL URLWithString:u] standardizedURL];
  71. }
  72. #pragma mark - FSM
  73. -(BOOL)exitIfError:(NSError *)error autoResume:(NSInteger)autoNextSteps
  74. {
  75. if( nil == error )
  76. return NO;
  77. self.error = error;
  78. state = Error;
  79. [self processState:autoNextSteps - 1];
  80. return YES;
  81. }
  82. -(void)resume
  83. {
  84. [self processState:1000];
  85. }
  86. -(void)processState:(NSInteger)autoNextSteps
  87. {
  88. MRLogD(@"%d %d", state, autoNextSteps, nil);
  89. if( 0 > autoNextSteps )
  90. return;
  91. NSParameterAssert( (Error != state && nil == self.error) || (Error == state && nil != self.error) );
  92. __weak typeof(self) weakSelf = self;
  93. NSURLSession *session = [NSURLSession sharedSession];
  94. NSParameterAssert(session.configuration);
  95. NSURL *ur = self.endpointURL;
  96. switch( state ) {
  97. case Start:
  98. state = GetLoginFormAndToken;
  99. [self processState:autoNextSteps - 1];
  100. return;
  101. case GetLoginFormAndToken:
  102. {
  103. // http://www.objc.io/issues/5-ios7/from-nsurlconnection-to-nsurlsession/
  104. // http://www.raywenderlich.com/51127/nsurlsession-tutorial
  105. NSURL *u = [ur urlForCommand:CMD_DO_LOGIN];
  106. MRLogD(@"%@ %@ %@", HTTP_GET, u, self.credential.user, nil);
  107. [[session dataTaskWithURL:u completionHandler:^(NSData * data, NSURLResponse * response, NSError * error) {
  108. // MRLogD (@"complete %@", response.URL, nil);
  109. if( error ) {
  110. // retry with (unsecure) http
  111. if( [HTTP_HTTPS isEqualToString:self.scheme] ) {
  112. self.scheme = HTTP_HTTP;
  113. state = GetLoginFormAndToken;
  114. [weakSelf processState:autoNextSteps - 1];
  115. return;
  116. }
  117. [weakSelf exitIfError:error autoResume:autoNextSteps - 1];
  118. return;
  119. }
  120. if( ![weakSelf parseAnyResponse:response data:data error:&error] ) {
  121. NSParameterAssert (error);
  122. [weakSelf exitIfError:error autoResume:autoNextSteps - 1];
  123. return;
  124. }
  125. if( self.hasLogOutLink ) {
  126. // use endpoint as it comes back from the server. Usually adds a trailing /
  127. self.endpoint = [response.URL stripSchemeAndCommand:CMD_DO_LOGIN];
  128. state = DoLogout;
  129. [weakSelf processState:autoNextSteps - 1];
  130. return;
  131. }
  132. weakSelf.title = [weakSelf fetchTitle:&error];
  133. if( [weakSelf exitIfError:error autoResume:autoNextSteps - 1] )
  134. return;
  135. weakSelf.formDict = [weakSelf fetchForm:&error];
  136. if( [weakSelf exitIfError:error autoResume:autoNextSteps - 1] )
  137. return;
  138. for( NSString * field in @[F_K_LOGIN, F_K_PASSWORD, F_K_TOKEN] ) {
  139. if( !weakSelf.formDict[field] ) {
  140. MRLogW (@"missing login form field: '%@'", field, nil);
  141. // MRLogW (@"response data: %@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding], nil);
  142. [weakSelf exitIfError:[NSError errorWithDomain:SHAARLI_ERROR_DOMAIN code:SHAARLI_ERROR_NO_TOKEN userInfo:@ { NSURLErrorKey:u, NSLocalizedDescriptionKey:NSLocalizedString (@"Most likely this isn't a shaarli backend.\n\n(The required login form field slot is missing.)", @"ShaarliCmdUpdateEndpoint") }
  143. ] autoResume:autoNextSteps - 1];
  144. return;
  145. }
  146. }
  147. // use endpoint as it comes back from the server. Usually adds a trailing /
  148. self.endpoint = [response.URL stripSchemeAndCommand:CMD_DO_LOGIN];
  149. state = PostLoginForm;
  150. [weakSelf processState:autoNextSteps - 1];
  151. }
  152. ] resume];
  153. }
  154. return;
  155. case DoLogout: {
  156. NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:[ur urlForCommand:CMD_DO_LOGOUT]];
  157. MRLogD (@"%@ %@", req.HTTPMethod, req.URL, nil);
  158. [[session dataTaskWithRequest:req completionHandler:^(NSData * data, NSURLResponse * response, NSError * error) {
  159. if( [weakSelf exitIfError:error autoResume:autoNextSteps - 1] )
  160. return;
  161. state = GetLoginFormAndToken;
  162. [weakSelf processState:autoNextSteps - 1];
  163. return;
  164. }
  165. ] resume];
  166. }
  167. return;
  168. case PostLoginForm: {
  169. NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:[ur urlForCommand:CMD_DO_LOGIN]];
  170. req.HTTPMethod = HTTP_POST;
  171. self.formDict[F_K_LOGIN] = self.credential.user;
  172. self.formDict[F_K_PASSWORD] = self.credential.password;
  173. self.formDict[F_K_RETURNURL] = [[ur urlForCommand:CMD_DO_CHANGEPASSWD] absoluteString];
  174. req.HTTPBody = [self.formDict postData];
  175. MRLogD (@"%@ %@", req.HTTPMethod, req.URL, nil);
  176. [[session dataTaskWithRequest:req completionHandler:^(NSData * data, NSURLResponse * response, NSError * error) {
  177. if( [weakSelf exitIfError:error autoResume:autoNextSteps - 1] )
  178. return;
  179. // MRLogD (@"%@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding], nil);
  180. [weakSelf receivedPost1Response:response data:data error:&error];
  181. if( [weakSelf exitIfError:error autoResume:autoNextSteps - 1] )
  182. return;
  183. if( !weakSelf.title ) {
  184. MRLogW (@"This is a bit od, there's no title yet.", nil);
  185. weakSelf.title = [weakSelf fetchTitle:&error];
  186. if( [weakSelf exitIfError:error autoResume:autoNextSteps - 1] )
  187. return;
  188. }
  189. if( weakSelf.hasLogOutLink ) {
  190. state = Success;
  191. [weakSelf processState:autoNextSteps - 1];
  192. return;
  193. }
  194. [weakSelf exitIfError:[NSError errorWithDomain:SHAARLI_ERROR_DOMAIN code:SHAARLI_ERROR_LOGOUT_BUTTON_EXPECTED userInfo:@ { NSURLErrorKey:req.URL, NSLocalizedDescriptionKey:NSLocalizedString (@"Couldn't find logout link, so maybe I'm not logged in properly.", @"ShaarliCmdUpdateEndpoint.m") }
  195. ] autoResume:autoNextSteps - 1];
  196. return;
  197. }
  198. ] resume];
  199. }
  200. return;
  201. case Error:
  202. NSParameterAssert (self.error);
  203. case Success:
  204. self.blockCompletion (self, self.error);
  205. state = Done;
  206. return;
  207. default:
  208. NSParameterAssert (NO);
  209. }
  210. }
  211. @end