toolkit/crashreporter/google-breakpad/src/client/mac/sender/uploader.mm

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 // Copyright (c) 2011, Google Inc.
     2 // All rights reserved.
     3 //
     4 // Redistribution and use in source and binary forms, with or without
     5 // modification, are permitted provided that the following conditions are
     6 // met:
     7 //
     8 //     * Redistributions of source code must retain the above copyright
     9 // notice, this list of conditions and the following disclaimer.
    10 //     * Redistributions in binary form must reproduce the above
    11 // copyright notice, this list of conditions and the following disclaimer
    12 // in the documentation and/or other materials provided with the
    13 // distribution.
    14 //     * Neither the name of Google Inc. nor the names of its
    15 // contributors may be used to endorse or promote products derived from
    16 // this software without specific prior written permission.
    17 //
    18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    19 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    20 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    21 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
    22 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    23 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
    24 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    25 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    26 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    27 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    28 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    30 #import <fcntl.h>
    31 #import <sys/stat.h>
    32 #include <TargetConditionals.h>
    33 #import <unistd.h>
    35 #import <SystemConfiguration/SystemConfiguration.h>
    37 #import "common/mac/HTTPMultipartUpload.h"
    39 #import "client/apple/Framework/BreakpadDefines.h"
    40 #import "client/mac/sender/uploader.h"
    41 #import "common/mac/GTMLogger.h"
    43 const int kMinidumpFileLengthLimit = 2 * 1024 * 1024;  // 2MB
    45 #define kApplePrefsSyncExcludeAllKey \
    46   @"com.apple.PreferenceSync.ExcludeAllSyncKeys"
    48 NSString *const kGoogleServerType = @"google";
    49 NSString *const kSocorroServerType = @"socorro";
    50 NSString *const kDefaultServerType = @"google";
    52 #pragma mark -
    54 namespace {
    55 // Read one line from the configuration file.
    56 NSString *readString(int fileId) {
    57   NSMutableString *str = [NSMutableString stringWithCapacity:32];
    58   char ch[2] = { 0 };
    60   while (read(fileId, &ch[0], 1) == 1) {
    61     if (ch[0] == '\n') {
    62       // Break if this is the first newline after reading some other string
    63       // data.
    64       if ([str length])
    65         break;
    66     } else {
    67       [str appendString:[NSString stringWithUTF8String:ch]];
    68     }
    69   }
    71   return str;
    72 }
    74 //=============================================================================
    75 // Read |length| of binary data from the configuration file. This method will
    76 // returns |nil| in case of error.
    77 NSData *readData(int fileId, ssize_t length) {
    78   NSMutableData *data = [NSMutableData dataWithLength:length];
    79   char *bytes = (char *)[data bytes];
    81   if (read(fileId, bytes, length) != length)
    82     return nil;
    84   return data;
    85 }
    87 //=============================================================================
    88 // Read the configuration from the config file.
    89 NSDictionary *readConfigurationData(const char *configFile) {
    90   int fileId = open(configFile, O_RDONLY, 0600);
    91   if (fileId == -1) {
    92     GTMLoggerDebug(@"Couldn't open config file %s - %s",
    93                    configFile,
    94                    strerror(errno));
    95   }
    97   // we want to avoid a build-up of old config files even if they
    98   // have been incorrectly written by the framework
    99   if (unlink(configFile)) {
   100     GTMLoggerDebug(@"Couldn't unlink config file %s - %s",
   101                    configFile,
   102                    strerror(errno));
   103   }
   105   if (fileId == -1) {
   106     return nil;
   107   }
   109   NSMutableDictionary *config = [NSMutableDictionary dictionary];
   111   while (1) {
   112     NSString *key = readString(fileId);
   114     if (![key length])
   115       break;
   117     // Read the data.  Try to convert to a UTF-8 string, or just save
   118     // the data
   119     NSString *lenStr = readString(fileId);
   120     ssize_t len = [lenStr intValue];
   121     NSData *data = readData(fileId, len);
   122     id value = [[NSString alloc] initWithData:data
   123                                      encoding:NSUTF8StringEncoding];
   125     [config setObject:(value ? value : data) forKey:key];
   126     [value release];
   127   }
   129   close(fileId);
   130   return config;
   131 }
   132 }  // namespace
   134 #pragma mark -
   136 @interface Uploader(PrivateMethods)
   138 // Update |parameters_| as well as the server parameters using |config|.
   139 - (void)translateConfigurationData:(NSDictionary *)config;
   141 // Read the minidump referenced in |parameters_| and update |minidumpContents_|
   142 // with its content.
   143 - (BOOL)readMinidumpData;
   145 // Read the log files referenced in |parameters_| and update |logFileData_|
   146 // with their content.
   147 - (BOOL)readLogFileData;
   149 // Returns a unique client id (user-specific), creating a persistent
   150 // one in the user defaults, if necessary.
   151 - (NSString*)clientID;
   153 // Returns a dictionary that can be used to map Breakpad parameter names to
   154 // URL parameter names.
   155 - (NSMutableDictionary *)dictionaryForServerType:(NSString *)serverType;
   157 // Helper method to set HTTP parameters based on server type.  This is
   158 // called right before the upload - crashParameters will contain, on exit,
   159 // URL parameters that should be sent with the minidump.
   160 - (BOOL)populateServerDictionary:(NSMutableDictionary *)crashParameters;
   162 // Initialization helper to create dictionaries mapping Breakpad
   163 // parameters to URL parameters
   164 - (void)createServerParameterDictionaries;
   166 // Accessor method for the URL parameter dictionary
   167 - (NSMutableDictionary *)urlParameterDictionary;
   169 // Records the uploaded crash ID to the log file.
   170 - (void)logUploadWithID:(const char *)uploadID;
   171 @end
   173 @implementation Uploader
   175 //=============================================================================
   176 - (id)initWithConfigFile:(const char *)configFile {
   177   NSDictionary *config = readConfigurationData(configFile);
   178   if (!config)
   179     return nil;
   181   return [self initWithConfig:config];
   182 }
   184 //=============================================================================
   185 - (id)initWithConfig:(NSDictionary *)config {
   186   if ((self = [super init])) {
   187     // Because the reporter is embedded in the framework (and many copies
   188     // of the framework may exist) its not completely certain that the OS
   189     // will obey the com.apple.PreferenceSync.ExcludeAllSyncKeys in our
   190     // Info.plist. To make sure, also set the key directly if needed.
   191     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
   192     if (![ud boolForKey:kApplePrefsSyncExcludeAllKey]) {
   193       [ud setBool:YES forKey:kApplePrefsSyncExcludeAllKey];
   194     }
   196     [self createServerParameterDictionaries];
   198     [self translateConfigurationData:config];
   200     // Read the minidump into memory.
   201     [self readMinidumpData];
   202     [self readLogFileData];
   203   }
   204   return self;
   205 }
   207 //=============================================================================
   208 - (void)translateConfigurationData:(NSDictionary *)config {
   209   parameters_ = [[NSMutableDictionary alloc] init];
   211   NSEnumerator *it = [config keyEnumerator];
   212   while (NSString *key = [it nextObject]) {
   213     // If the keyname is prefixed by BREAKPAD_SERVER_PARAMETER_PREFIX
   214     // that indicates that it should be uploaded to the server along
   215     // with the minidump, so we treat it specially.
   216     if ([key hasPrefix:@BREAKPAD_SERVER_PARAMETER_PREFIX]) {
   217       NSString *urlParameterKey =
   218         [key substringFromIndex:[@BREAKPAD_SERVER_PARAMETER_PREFIX length]];
   219       if ([urlParameterKey length]) {
   220         id value = [config objectForKey:key];
   221         if ([value isKindOfClass:[NSString class]]) {
   222           [self addServerParameter:(NSString *)value
   223                             forKey:urlParameterKey];
   224         } else {
   225           [self addServerParameter:(NSData *)value
   226                             forKey:urlParameterKey];
   227         }
   228       }
   229     } else {
   230       [parameters_ setObject:[config objectForKey:key] forKey:key];
   231     }
   232   }
   234   // generate a unique client ID based on this host's MAC address
   235   // then add a key/value pair for it
   236   NSString *clientID = [self clientID];
   237   [parameters_ setObject:clientID forKey:@"guid"];
   238 }
   240 // Per user per machine
   241 - (NSString *)clientID {
   242   NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
   243   NSString *crashClientID = [ud stringForKey:kClientIdPreferenceKey];
   244   if (crashClientID) {
   245     return crashClientID;
   246   }
   248   // Otherwise, if we have no client id, generate one!
   249   srandom((int)[[NSDate date] timeIntervalSince1970]);
   250   long clientId1 = random();
   251   long clientId2 = random();
   252   long clientId3 = random();
   253   crashClientID = [NSString stringWithFormat:@"%lx%lx%lx",
   254                             clientId1, clientId2, clientId3];
   256   [ud setObject:crashClientID forKey:kClientIdPreferenceKey];
   257   [ud synchronize];
   258   return crashClientID;
   259 }
   261 //=============================================================================
   262 - (BOOL)readLogFileData {
   263 #if TARGET_OS_IPHONE
   264   return NO;
   265 #else
   266   unsigned int logFileCounter = 0;
   268   NSString *logPath;
   269   size_t logFileTailSize =
   270       [[parameters_ objectForKey:@BREAKPAD_LOGFILE_UPLOAD_SIZE] intValue];
   272   NSMutableArray *logFilenames; // An array of NSString, one per log file
   273   logFilenames = [[NSMutableArray alloc] init];
   275   char tmpDirTemplate[80] = "/tmp/CrashUpload-XXXXX";
   276   char *tmpDir = mkdtemp(tmpDirTemplate);
   278   // Construct key names for the keys we expect to contain log file paths
   279   for(logFileCounter = 0;; logFileCounter++) {
   280     NSString *logFileKey = [NSString stringWithFormat:@"%@%d",
   281                                      @BREAKPAD_LOGFILE_KEY_PREFIX,
   282                                      logFileCounter];
   284     logPath = [parameters_ objectForKey:logFileKey];
   286     // They should all be consecutive, so if we don't find one, assume
   287     // we're done
   289     if (!logPath) {
   290       break;
   291     }
   293     NSData *entireLogFile = [[NSData alloc] initWithContentsOfFile:logPath];
   295     if (entireLogFile == nil) {
   296       continue;
   297     }
   299     NSRange fileRange;
   301     // Truncate the log file, only if necessary
   303     if ([entireLogFile length] <= logFileTailSize) {
   304       fileRange = NSMakeRange(0, [entireLogFile length]);
   305     } else {
   306       fileRange = NSMakeRange([entireLogFile length] - logFileTailSize,
   307                               logFileTailSize);
   308     }
   310     char tmpFilenameTemplate[100];
   312     // Generate a template based on the log filename
   313     sprintf(tmpFilenameTemplate,"%s/%s-XXXX", tmpDir,
   314             [[logPath lastPathComponent] fileSystemRepresentation]);
   316     char *tmpFile = mktemp(tmpFilenameTemplate);
   318     NSData *logSubdata = [entireLogFile subdataWithRange:fileRange];
   319     NSString *tmpFileString = [NSString stringWithUTF8String:tmpFile];
   320     [logSubdata writeToFile:tmpFileString atomically:NO];
   322     [logFilenames addObject:[tmpFileString lastPathComponent]];
   323     [entireLogFile release];
   324   }
   326   if ([logFilenames count] == 0) {
   327     [logFilenames release];
   328     logFileData_ =  nil;
   329     return NO;
   330   }
   332   // now, bzip all files into one
   333   NSTask *tarTask = [[NSTask alloc] init];
   335   [tarTask setCurrentDirectoryPath:[NSString stringWithUTF8String:tmpDir]];
   336   [tarTask setLaunchPath:@"/usr/bin/tar"];
   338   NSMutableArray *bzipArgs = [NSMutableArray arrayWithObjects:@"-cjvf",
   339                                              @"log.tar.bz2",nil];
   340   [bzipArgs addObjectsFromArray:logFilenames];
   342   [logFilenames release];
   344   [tarTask setArguments:bzipArgs];
   345   [tarTask launch];
   346   [tarTask waitUntilExit];
   347   [tarTask release];
   349   NSString *logTarFile = [NSString stringWithFormat:@"%s/log.tar.bz2",tmpDir];
   350   logFileData_ = [[NSData alloc] initWithContentsOfFile:logTarFile];
   351   if (logFileData_ == nil) {
   352     GTMLoggerDebug(@"Cannot find temp tar log file: %@", logTarFile);
   353     return NO;
   354   }
   355   return YES;
   356 #endif  // TARGET_OS_IPHONE
   357 }
   359 //=============================================================================
   360 - (BOOL)readMinidumpData {
   361   NSString *minidumpDir =
   362       [parameters_ objectForKey:@kReporterMinidumpDirectoryKey];
   363   NSString *minidumpID = [parameters_ objectForKey:@kReporterMinidumpIDKey];
   365   if (![minidumpID length])
   366     return NO;
   368   NSString *path = [minidumpDir stringByAppendingPathComponent:minidumpID];
   369   path = [path stringByAppendingPathExtension:@"dmp"];
   371   // check the size of the minidump and limit it to a reasonable size
   372   // before attempting to load into memory and upload
   373   const char *fileName = [path fileSystemRepresentation];
   374   struct stat fileStatus;
   376   BOOL success = YES;
   378   if (!stat(fileName, &fileStatus)) {
   379     if (fileStatus.st_size > kMinidumpFileLengthLimit) {
   380       fprintf(stderr, "Breakpad Uploader: minidump file too large " \
   381               "to upload : %d\n", (int)fileStatus.st_size);
   382       success = NO;
   383     }
   384   } else {
   385       fprintf(stderr, "Breakpad Uploader: unable to determine minidump " \
   386               "file length\n");
   387       success = NO;
   388   }
   390   if (success) {
   391     minidumpContents_ = [[NSData alloc] initWithContentsOfFile:path];
   392     success = ([minidumpContents_ length] ? YES : NO);
   393   }
   395   if (!success) {
   396     // something wrong with the minidump file -- delete it
   397     unlink(fileName);
   398   }
   400   return success;
   401 }
   403 #pragma mark -
   404 //=============================================================================
   406 - (void)createServerParameterDictionaries {
   407   serverDictionary_ = [[NSMutableDictionary alloc] init];
   408   socorroDictionary_ = [[NSMutableDictionary alloc] init];
   409   googleDictionary_ = [[NSMutableDictionary alloc] init];
   410   extraServerVars_ = [[NSMutableDictionary alloc] init];
   412   [serverDictionary_ setObject:socorroDictionary_ forKey:kSocorroServerType];
   413   [serverDictionary_ setObject:googleDictionary_ forKey:kGoogleServerType];
   415   [googleDictionary_ setObject:@"ptime" forKey:@BREAKPAD_PROCESS_UP_TIME];
   416   [googleDictionary_ setObject:@"email" forKey:@BREAKPAD_EMAIL];
   417   [googleDictionary_ setObject:@"comments" forKey:@BREAKPAD_COMMENTS];
   418   [googleDictionary_ setObject:@"prod" forKey:@BREAKPAD_PRODUCT];
   419   [googleDictionary_ setObject:@"ver" forKey:@BREAKPAD_VERSION];
   420   [googleDictionary_ setObject:@"guid" forKey:@"guid"];
   422   [socorroDictionary_ setObject:@"Comments" forKey:@BREAKPAD_COMMENTS];
   423   [socorroDictionary_ setObject:@"CrashTime"
   424                          forKey:@BREAKPAD_PROCESS_CRASH_TIME];
   425   [socorroDictionary_ setObject:@"StartupTime"
   426                          forKey:@BREAKPAD_PROCESS_START_TIME];
   427   [socorroDictionary_ setObject:@"Version"
   428                          forKey:@BREAKPAD_VERSION];
   429   [socorroDictionary_ setObject:@"ProductName"
   430                          forKey:@BREAKPAD_PRODUCT];
   431   [socorroDictionary_ setObject:@"Email"
   432                          forKey:@BREAKPAD_EMAIL];
   433 }
   435 - (NSMutableDictionary *)dictionaryForServerType:(NSString *)serverType {
   436   if (serverType == nil || [serverType length] == 0) {
   437     return [serverDictionary_ objectForKey:kDefaultServerType];
   438   }
   439   return [serverDictionary_ objectForKey:serverType];
   440 }
   442 - (NSMutableDictionary *)urlParameterDictionary {
   443   NSString *serverType = [parameters_ objectForKey:@BREAKPAD_SERVER_TYPE];
   444   return [self dictionaryForServerType:serverType];
   446 }
   448 - (BOOL)populateServerDictionary:(NSMutableDictionary *)crashParameters {
   449   NSDictionary *urlParameterNames = [self urlParameterDictionary];
   451   id key;
   452   NSEnumerator *enumerator = [parameters_ keyEnumerator];
   454   while ((key = [enumerator nextObject])) {
   455     // The key from parameters_ corresponds to a key in
   456     // urlParameterNames.  The value in parameters_ gets stored in
   457     // crashParameters with a key that is the value in
   458     // urlParameterNames.
   460     // For instance, if parameters_ has [PRODUCT_NAME => "FOOBAR"] and
   461     // urlParameterNames has [PRODUCT_NAME => "pname"] the final HTTP
   462     // URL parameter becomes [pname => "FOOBAR"].
   463     NSString *breakpadParameterName = (NSString *)key;
   464     NSString *urlParameter = [urlParameterNames
   465                                    objectForKey:breakpadParameterName];
   466     if (urlParameter) {
   467       [crashParameters setObject:[parameters_ objectForKey:key]
   468                           forKey:urlParameter];
   469     }
   470   }
   472   // Now, add the parameters that were added by the application.
   473   enumerator = [extraServerVars_ keyEnumerator];
   475   while ((key = [enumerator nextObject])) {
   476     NSString *urlParameterName = (NSString *)key;
   477     NSString *urlParameterValue =
   478       [extraServerVars_ objectForKey:urlParameterName];
   479     [crashParameters setObject:urlParameterValue
   480                         forKey:urlParameterName];
   481   }
   482   return YES;
   483 }
   485 - (void)addServerParameter:(id)value forKey:(NSString *)key {
   486   [extraServerVars_ setObject:value forKey:key];
   487 }
   489 //=============================================================================
   490 - (void)report {
   491   NSURL *url = [NSURL URLWithString:[parameters_ objectForKey:@BREAKPAD_URL]];
   492   HTTPMultipartUpload *upload = [[HTTPMultipartUpload alloc] initWithURL:url];
   493   NSMutableDictionary *uploadParameters = [NSMutableDictionary dictionary];
   495   if (![self populateServerDictionary:uploadParameters]) {
   496     [upload release];
   497     return;
   498   }
   500   [upload setParameters:uploadParameters];
   502   // Add minidump file
   503   if (minidumpContents_) {
   504     [upload addFileContents:minidumpContents_ name:@"upload_file_minidump"];
   506     // If there is a log file, upload it together with the minidump.
   507     if (logFileData_) {
   508       [upload addFileContents:logFileData_ name:@"log"];
   509     }
   511     // Send it
   512     NSError *error = nil;
   513     NSData *data = [upload send:&error];
   514     NSString *result = [[NSString alloc] initWithData:data
   515                                          encoding:NSUTF8StringEncoding];
   516     const char *reportID = "ERR";
   518     if (error) {
   519       fprintf(stderr, "Breakpad Uploader: Send Error: %s\n",
   520               [[error description] UTF8String]);
   521     } else {
   522       NSCharacterSet *trimSet =
   523           [NSCharacterSet whitespaceAndNewlineCharacterSet];
   524       reportID = [[result stringByTrimmingCharactersInSet:trimSet] UTF8String];
   525       [self logUploadWithID:reportID];
   526     }
   528     // rename the minidump file according to the id returned from the server
   529     NSString *minidumpDir =
   530         [parameters_ objectForKey:@kReporterMinidumpDirectoryKey];
   531     NSString *minidumpID = [parameters_ objectForKey:@kReporterMinidumpIDKey];
   533     NSString *srcString = [NSString stringWithFormat:@"%@/%@.dmp",
   534                                     minidumpDir, minidumpID];
   535     NSString *destString = [NSString stringWithFormat:@"%@/%s.dmp",
   536                                      minidumpDir, reportID];
   538     const char *src = [srcString fileSystemRepresentation];
   539     const char *dest = [destString fileSystemRepresentation];
   541     if (rename(src, dest) == 0) {
   542       GTMLoggerInfo(@"Breakpad Uploader: Renamed %s to %s after successful " \
   543                     "upload",src, dest);
   544     }
   545     else {
   546       // can't rename - don't worry - it's not important for users
   547       GTMLoggerDebug(@"Breakpad Uploader: successful upload report ID = %s\n",
   548                      reportID );
   549     }
   550     [result release];
   551   } else {
   552     // Minidump is missing -- upload just the log file.
   553     if (logFileData_) {
   554       [self uploadData:logFileData_ name:@"log"];
   555     }
   556   }
   557   [upload release];
   558 }
   560 - (void)uploadData:(NSData *)data name:(NSString *)name {
   561   NSURL *url = [NSURL URLWithString:[parameters_ objectForKey:@BREAKPAD_URL]];
   562   NSMutableDictionary *uploadParameters = [NSMutableDictionary dictionary];
   564   if (![self populateServerDictionary:uploadParameters])
   565     return;
   567   HTTPMultipartUpload *upload =
   568       [[HTTPMultipartUpload alloc] initWithURL:url];
   570   [uploadParameters setObject:name forKey:@"type"];
   571   [upload setParameters:uploadParameters];
   572   [upload addFileContents:data name:name];
   574   [upload send:nil];
   575   [upload release];
   576 }
   578 - (void)logUploadWithID:(const char *)uploadID {
   579   NSString *minidumpDir =
   580       [parameters_ objectForKey:@kReporterMinidumpDirectoryKey];
   581   NSString *logFilePath = [NSString stringWithFormat:@"%@/%s",
   582       minidumpDir, kReporterLogFilename];
   583   NSString *logLine = [NSString stringWithFormat:@"%0.f,%s\n",
   584       [[NSDate date] timeIntervalSince1970], uploadID];
   585   NSData *logData = [logLine dataUsingEncoding:NSUTF8StringEncoding];
   587   NSFileManager *fileManager = [NSFileManager defaultManager];
   588   if ([fileManager fileExistsAtPath:logFilePath]) {
   589     NSFileHandle *logFileHandle =
   590        [NSFileHandle fileHandleForWritingAtPath:logFilePath];
   591     [logFileHandle seekToEndOfFile];
   592     [logFileHandle writeData:logData];
   593     [logFileHandle closeFile];
   594   } else {
   595     [fileManager createFileAtPath:logFilePath
   596                          contents:logData
   597                        attributes:nil];
   598   }
   599 }
   601 //=============================================================================
   602 - (NSMutableDictionary *)parameters {
   603   return parameters_;
   604 }
   606 //=============================================================================
   607 - (void)dealloc {
   608   [parameters_ release];
   609   [minidumpContents_ release];
   610   [logFileData_ release];
   611   [googleDictionary_ release];
   612   [socorroDictionary_ release];
   613   [serverDictionary_ release];
   614   [extraServerVars_ release];
   615   [super dealloc];
   616 }
   618 @end

mercurial