source: trunk/PrefsPane/src/ServerCommunication.m @ 1046

Revision 1007, 11.4 KB checked in by speck, 10 months ago (diff)

Fix problem with reactivation of GlimmerBlocker?: "can't connect to host" dialog. Reported by John B. Matthews.

Line 
1/* Copyright (C) 2008-2009 Peter Speck
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 3 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#import "ServerCommunication.h"
18#import "GBDebug.h"
19#import "Prefs.h"
20#import "XmlDoc.h"
21#import "Xml.h"
22#import "AlertHelper.h"
23
24#ifdef DEBUG
25//#define DEBUG_COMM 1
26#endif
27
28static NSString* javaServerLogPath = @"/Library/Logs/GlimmerBlocker/GlimmerBlocker.log";
29
30@implementation ServerCommunication
31
32- (id)initWithPrefs:(Prefs*)prefs
33{
34    if ((self = [super init])) {
35        _prefs = [prefs retain];
36        _alertHelper = [[prefs alertHelper] retain];
37        _publicAdminKey = [[_prefs stringForXPath:@"proxy-server/@info-admin-key"] retain];
38        if (!_publicAdminKey) {
39            NSMutableString *sb = [NSMutableString stringWithCapacity:40];
40            for (int i = 0; i < 10; i++)
41                [sb appendFormat:@"%04x", arc4random() & 0xFFFF];
42            _publicAdminKey = [[NSString stringWithString:sb] retain];
43            [_prefs setString:_publicAdminKey forXPath:@"proxy-server/@info-admin-key"];
44            [_prefs markAsDirty];
45            DebugNSLog(@"adminHost: %@", _publicAdminKey);
46        }
47    }
48    return self;
49}
50
51- (void)dealloc
52{
53    [_prefs release];
54    [_alertHelper release];
55    [_publicAdminKey release];
56    [_secureAdminHost release];
57    [super dealloc];
58}
59
60- (void)performPostLoadPreferencePanelCheck
61{
62/*
63    if (![self isServerRunning] || ![_launchctl needsUpdatePlist])
64        return;
65    [_alertHelper prepareCriticalAlertWithTitle:@"GlimmerBlocker needs to be restarted"
66                            withInformativeText:@"Please deactivate and reactivate GlimmerBlocker."];
67    [_alertHelper showPreparedAlert];
68*/
69}
70
71- (Prefs*)prefs
72{
73    return _prefs;
74}
75
76#pragma mark ------------------------------------- URLs
77
78- (NSString*)publicAdminKey
79{
80    return _publicAdminKey;
81}
82
83- (BOOL)getSecureAdminKey
84{
85    if (_secureAdminHost)
86        return YES;
87    NSMutableData* data = [NSMutableData dataWithCapacity:200];
88    int stat = [_prefs runAuthHelperWithArgs:[NSArray arrayWithObject:@"--java-settings"]
89                               withInputData:NULL
90                              withOutputData:data
91                                    withAuth:YES];
92    if (stat) {
93        DebugNSLog(@"Got error status %d from AuthHelper --java-settings", stat);
94        return NO;
95    }
96    NSURL* url = [NSURL URLWithString:@"x-file:java-settings.xml"];
97    XmlDoc* doc = [[[XmlDoc alloc] initWithURL:url withData:data] autorelease];
98    if (![doc doc] || ![doc rootE]) {
99        NSLog(@"Can't parse java-settings xml.");
100        return NO;
101    }
102    _secureAdminHost = [[doc stringForXPath:@"proxy/@secure-admin-key"] retain];
103    return YES;
104}
105
106- (NSString*)getAdminUrl:(NSString*)pathArgs
107{
108    NSString* key = _publicAdminKey;
109    if ([pathArgs hasPrefix:@"!"]) {
110        pathArgs = [pathArgs substringFromIndex:1];
111        if ([self getSecureAdminKey]) {
112            key = _secureAdminHost;
113        } else {
114            NSLog(@"Can't get secure-admin host for: %@", pathArgs);
115            key = @"failed-secure-admin";
116        }
117    }
118    return [NSString stringWithFormat:@"http://127.0.0.1:%d/=gb=/%@%@", [self getPortNumber], key, pathArgs];
119}
120
121#pragma mark ------------------------------------- updater
122
123- (void)updateUpdaterLaunchd
124{
125    [self saveDirtyPrefsAndNotify];
126    BOOL run = [_prefs boolForXPath:@"updater/@check-in-background"];
127    if (!run) {
128        NSArray* args = [NSArray arrayWithObjects:@"--updater-stop", NULL];
129        [_prefs runAuthHelperWithArgs:(NSArray*)args withInputData:NULL withAuth:YES];
130        return;
131    }
132    BOOL beta = [_prefs boolForXPath:@"updater/@use-beta-versions"];
133    NSArray* args = [NSArray arrayWithObjects:@"--updater-start", (beta ? @"--beta" : @"--final"), NULL];
134    [_prefs runAuthHelperWithArgs:(NSArray*)args withInputData:NULL withAuth:YES];
135}
136
137- (void)launchUpdaterApp
138{
139    // [NSApp activateIgnoringOtherApps:YES] does not bring it to foreground in 10.7 when launched using launchctl.
140    // so always launch manually using /usr/bin/open.
141    BOOL beta = [_prefs boolForXPath:@"updater/@use-beta-versions"];
142    NSString *appPath = [GBDebug updaterBundlePath];
143    NSArray *args = [NSArray arrayWithObjects:@"-a", appPath, NULL];
144    if (beta)
145        args = [args arrayByAddingObject:[NSString stringWithFormat:@"%@/Contents/Resources/option-beta.txt", [GBDebug updaterBundlePath]]];
146    [GBDebug executeCommandlineTool:@"/usr/bin/open" withArgs:args];
147}
148           
149
150#pragma mark ------------------------------------- server management
151- (BOOL)isServerRunning
152{
153    // a real check is more expensive.
154    NSString* path = @"/Library/LaunchDaemons/org.glimmerblocker.proxy.plist";
155    return [[NSFileManager defaultManager] fileExistsAtPath:path];
156    // We could do
157    //   sudo launchctl list org.glimmerblocker.proxy
158    // but the id-param is totally undocumented.
159    // So we'd have to check if anything runs at the port,
160    // or using a full "launchctl list".
161}
162
163- (void)startServer
164{
165    [_prefs saveIfDirty:self];
166    NSArray* args = [NSArray arrayWithObjects:@"--start", NULL];
167    [_prefs runAuthHelperWithArgs:(NSArray*)args withInputData:NULL withAuth:YES];
168    //
169    // The server might take some time, and loading filters will fail with an error dialog if it's
170    // not ready in time.
171    // So keep waiting a bit until the server responds to http.
172    NSString* pathArgs = @"/test-running";
173    NSURLResponse *response;
174    NSError *errorRef;
175    int i = 0;
176    while (true) {
177        if ([self curlToData:pathArgs withResponse:&response withErrorRef:&errorRef]) {
178            DebugNSLog(@"Server now ready after %d retries.", i);
179            break;
180        }
181        if (++i > 20) {
182            NSLog(@"Proxy server doesn't seem to come online.");
183            break;
184        }
185        [NSThread sleepForTimeInterval:0.25];
186    }
187    [NSThread sleepForTimeInterval:0.25];
188    //
189    [self updateUpdaterLaunchd];
190}
191
192- (void)stopServer
193{
194    [_prefs saveIfDirty:self];
195    [self updateUpdaterLaunchd];
196    //
197    if ([self isServerRunning]) {
198        NSError* error = NULL;
199        [self curl:@"!/stop-notification" withErrorRef:&error];
200        [NSThread sleepForTimeInterval:0.1]; // give server some time to log the event.
201    }
202    NSArray* args = [NSArray arrayWithObjects:@"--stop", NULL];
203    [_prefs runAuthHelperWithArgs:(NSArray*)args withInputData:NULL withAuth:YES];
204}
205
206- (int)getPortNumber
207{
208    return [_prefs intForXPath:@"proxy-server/@listen-port"];
209}
210
211- (void)openServerLogInConsole
212{
213    NSArray* args = [[NSArray arrayWithObjects:@"-a", @"Console", javaServerLogPath, nil] retain];
214    NSTask* task = [[NSTask alloc] init];
215    [task setLaunchPath:@"/usr/bin/open"];
216    [task setArguments:args];
217    [task setCurrentDirectoryPath:@"/tmp"];
218    [task launch];
219    [task waitUntilExit];
220    int status = [task terminationStatus];
221    //DebugNSLog(@"task rc = %d", [task retainCount]);
222    [task release];
223    [args release];
224    if (status)
225        NSLog(@"Execute '/usr/bin/open %@' failed with exit code %d", javaServerLogPath, status);
226}
227
228- (void)sendReloadPreferencesForModifiedFilter:(int)filterId withRule:(int)ruleId;
229{
230    if (![self isServerRunning])
231        return;
232    NSString* url = @"!/reload-panel-settings";
233    if (filterId > 0)
234        url = [NSString stringWithFormat:@"%@?filter=%d&rule=%d", url, filterId, ruleId];
235    [self curl:url];
236}
237
238- (void)saveDirtyPrefsAndNotify
239{
240    [_prefs saveIfDirty:self];
241    [self sendReloadPreferencesForModifiedFilter:0 withRule:0];
242}
243
244#pragma mark ------------------------------------- Show error sheet
245
246- (void)showServerErrorAlertWithUrl:(NSURL*)url
247                          withError:(NSError*)error
248{
249    NSString* msg = [NSString stringWithFormat:@"%@\n\nURL: %@",
250                     @"Try deactivate and reactivate GlimmerBlocker",
251                     [url path]];
252    if (error)
253        msg = [NSString stringWithFormat:@"%@\n\n%@", msg, error];
254    NSLog(@"GB server error: %@", msg);
255    [_alertHelper prepareCriticalAlertWithTitle:@"Problem performing the request"
256                            withInformativeText:msg];
257    [_alertHelper showPreparedAlert];
258    NSLog(@"GlimmerBlocker download of URL failed: %@", url);
259    NSLog(@"Error: %@", error);
260}
261
262#pragma mark ------------------------------------- curl, NSData
263
264- (NSData*)curlToData:(NSString*)pathArgs
265         withResponse:(NSURLResponse**)response
266         withErrorRef:(NSError**)errorRef
267{
268    *response = NULL;
269    if (errorRef)
270        *errorRef = NULL;
271    NSData* postData = NULL;
272    NSRange r = [pathArgs rangeOfString:@"?"];
273    if (r.location != NSNotFound && [pathArgs length] - r.location > 100) {
274        postData = [[pathArgs substringFromIndex:r.location + 1] dataUsingEncoding:NSUTF8StringEncoding];
275        pathArgs = [pathArgs substringToIndex:r.location];
276    }
277    NSURL* url = [NSURL URLWithString:[self getAdminUrl:pathArgs]];
278    NSMutableURLRequest* req = [[NSMutableURLRequest alloc ] initWithURL:url];
279    [req setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData];
280    double timeout = [pathArgs hasPrefix:@"/test-running"] ? 1.0 : 60.0;
281    [req setTimeoutInterval:timeout];
282    if (postData) {
283        [req setHTTPMethod:@"POST"];
284        [req setValue:@"application/x-www-form-urlencoded; charset=UTF-8" forHTTPHeaderField:@"Content-Type"];
285        [req setHTTPBody:postData];
286    }
287    NSError* error = NULL;
288    NSError **tmpRef = (errorRef ? errorRef : &error);
289    NSData* data = [NSURLConnection sendSynchronousRequest:req returningResponse:response error:tmpRef];
290    [req release];
291    if (!data || !*response || *tmpRef) {
292        if (errorRef)
293            return NULL;
294        [self showServerErrorAlertWithUrl:url withError:error];
295        if (!response)
296            NSLog(@"  didn't get any response object.");
297        return NULL;
298    }
299#ifdef DEBUG_COMM
300    DebugNSLog(@"Did download URL: %@", url);
301#endif
302    return data;
303}
304
305#pragma mark ------------------------------------- curl, NSString
306
307- (NSString*)curl:(NSString*)pathArgs
308{
309    return [self curl:pathArgs withErrorRef:NULL];
310}
311
312- (NSString*)curl:(NSString*)pathArgs
313     withErrorRef:(NSError**)errorRef
314{
315    NSURLResponse *response = NULL;
316    NSData* data = [self curlToData:pathArgs withResponse:&response withErrorRef:errorRef];
317    if (!data)
318        return NULL;
319#ifdef DEBUG_COMM
320    DebugNSLog(@"Did download URL: %@", pathArgs);
321    DebugNSLog(@"  textEncodingName: %@", [response textEncodingName]);
322#endif
323    NSString* s = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
324#if DEBUG_COMM
325    DebugNSLog(@"Result: {{{%@}}}", s);
326#endif
327    return s;
328}
329
330#pragma mark ------------------------------------- curl, XmlDoc
331
332- (XmlDoc*)curlToXml:(NSString*)pathArgs withActionDescription:(NSString*)actionDesc
333{
334    return [self curlToXml:pathArgs withActionDescription:actionDesc withErrorRef:NULL];
335}
336
337- (XmlDoc*)curlToXml:(NSString*)pathArgs withActionDescription:(NSString*)actionDesc
338        withErrorRef:(NSError**)errorRef;
339{
340    NSURLResponse *response = NULL;
341    NSData* data = [self curlToData:pathArgs withResponse:&response withErrorRef:errorRef];
342    if (!data)
343        return NULL;
344    NSURL* url = [NSURL URLWithString:[self getAdminUrl:pathArgs]];
345    XmlDoc* doc = [[[XmlDoc alloc] initWithURL:url withData:data] autorelease];
346    if (![doc rootE])
347        return NULL;
348    NSString* error = [Xml getAttribute:[doc rootE] withName:@"error"];
349    if (error) {
350        if (errorRef) {
351            NSDictionary* dict = [NSDictionary dictionaryWithObject:error forKey:NSLocalizedDescriptionKey];
352            NSString* errorDomain = @"dk.vitality.glimmerblocker";
353            *errorRef = [NSError errorWithDomain:errorDomain code:-1 userInfo:dict];
354            return NULL;
355        }
356        if (actionDesc) {
357            [_alertHelper prepareCriticalAlertWithTitle:actionDesc
358                                    withInformativeText:error];
359            [_alertHelper showPreparedAlert];
360        } else {
361            NSLog(@"Got http error in curlXml without actionDesc: %@", error);
362        }
363        return NULL;
364    }
365    return doc;
366}
367
368@end
Note: See TracBrowser for help on using the repository browser.