/thirdparty/breakpad/client/mac/sender/crash_report_sender.m

http://github.com/tomahawk-player/tomahawk · Objective C · 753 lines · 490 code · 115 blank · 148 comment · 77 complexity · c671d46572194b8816a59cbfcbda56fa MD5 · raw file

  1. // Copyright (c) 2006, 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.
  29. #import "client/mac/sender/crash_report_sender.h"
  30. #import <Cocoa/Cocoa.h>
  31. #import <pwd.h>
  32. #import <sys/stat.h>
  33. #import <SystemConfiguration/SystemConfiguration.h>
  34. #import <unistd.h>
  35. #import "client/apple/Framework/BreakpadDefines.h"
  36. #import "common/mac/GTMLogger.h"
  37. #import "common/mac/HTTPMultipartUpload.h"
  38. #define kLastSubmission @"LastSubmission"
  39. const int kUserCommentsMaxLength = 1500;
  40. const int kEmailMaxLength = 64;
  41. #define kApplePrefsSyncExcludeAllKey \
  42. @"com.apple.PreferenceSync.ExcludeAllSyncKeys"
  43. #pragma mark -
  44. @interface NSView (ResizabilityExtentions)
  45. // Shifts the view vertically by the given amount.
  46. - (void)breakpad_shiftVertically:(CGFloat)offset;
  47. // Shifts the view horizontally by the given amount.
  48. - (void)breakpad_shiftHorizontally:(CGFloat)offset;
  49. @end
  50. @implementation NSView (ResizabilityExtentions)
  51. - (void)breakpad_shiftVertically:(CGFloat)offset {
  52. NSPoint origin = [self frame].origin;
  53. origin.y += offset;
  54. [self setFrameOrigin:origin];
  55. }
  56. - (void)breakpad_shiftHorizontally:(CGFloat)offset {
  57. NSPoint origin = [self frame].origin;
  58. origin.x += offset;
  59. [self setFrameOrigin:origin];
  60. }
  61. @end
  62. @interface NSWindow (ResizabilityExtentions)
  63. // Adjusts the window height by heightDelta relative to its current height,
  64. // keeping all the content at the same size.
  65. - (void)breakpad_adjustHeight:(CGFloat)heightDelta;
  66. @end
  67. @implementation NSWindow (ResizabilityExtentions)
  68. - (void)breakpad_adjustHeight:(CGFloat)heightDelta {
  69. [[self contentView] setAutoresizesSubviews:NO];
  70. NSRect windowFrame = [self frame];
  71. windowFrame.size.height += heightDelta;
  72. [self setFrame:windowFrame display:YES];
  73. // For some reason the content view is resizing, but not adjusting its origin,
  74. // so correct it manually.
  75. [[self contentView] setFrameOrigin:NSMakePoint(0, 0)];
  76. [[self contentView] setAutoresizesSubviews:YES];
  77. }
  78. @end
  79. @interface NSTextField (ResizabilityExtentions)
  80. // Grows or shrinks the height of the field to the minimum required to show the
  81. // current text, preserving the existing width and origin.
  82. // Returns the change in height.
  83. - (CGFloat)breakpad_adjustHeightToFit;
  84. // Grows or shrinks the width of the field to the minimum required to show the
  85. // current text, preserving the existing height and origin.
  86. // Returns the change in width.
  87. - (CGFloat)breakpad_adjustWidthToFit;
  88. @end
  89. @implementation NSTextField (ResizabilityExtentions)
  90. - (CGFloat)breakpad_adjustHeightToFit {
  91. NSRect oldFrame = [self frame];
  92. // Starting with the 10.5 SDK, height won't grow, so make it huge to start.
  93. NSRect presizeFrame = oldFrame;
  94. presizeFrame.size.height = MAXFLOAT;
  95. // sizeToFit will blow out the width rather than making the field taller, so
  96. // we do it manually.
  97. NSSize newSize = [[self cell] cellSizeForBounds:presizeFrame];
  98. NSRect newFrame = NSMakeRect(oldFrame.origin.x, oldFrame.origin.y,
  99. NSWidth(oldFrame), newSize.height);
  100. [self setFrame:newFrame];
  101. return newSize.height - NSHeight(oldFrame);
  102. }
  103. - (CGFloat)breakpad_adjustWidthToFit {
  104. NSRect oldFrame = [self frame];
  105. [self sizeToFit];
  106. return NSWidth([self frame]) - NSWidth(oldFrame);
  107. }
  108. @end
  109. @interface NSButton (ResizabilityExtentions)
  110. // Resizes to fit the label using IB-style size-to-fit metrics and enforcing a
  111. // minimum width of 70, while preserving the right edge location.
  112. // Returns the change in width.
  113. - (CGFloat)breakpad_smartSizeToFit;
  114. @end
  115. @implementation NSButton (ResizabilityExtentions)
  116. - (CGFloat)breakpad_smartSizeToFit {
  117. NSRect oldFrame = [self frame];
  118. [self sizeToFit];
  119. NSRect newFrame = [self frame];
  120. // sizeToFit gives much worse results that IB's Size to Fit option. This is
  121. // the amount of padding IB adds over a sizeToFit, empirically determined.
  122. const float kExtraPaddingAmount = 12;
  123. const float kMinButtonWidth = 70; // The default button size in IB.
  124. newFrame.size.width = NSWidth(newFrame) + kExtraPaddingAmount;
  125. if (NSWidth(newFrame) < kMinButtonWidth)
  126. newFrame.size.width = kMinButtonWidth;
  127. // Preserve the right edge location.
  128. newFrame.origin.x = NSMaxX(oldFrame) - NSWidth(newFrame);
  129. [self setFrame:newFrame];
  130. return NSWidth(newFrame) - NSWidth(oldFrame);
  131. }
  132. @end
  133. #pragma mark -
  134. @interface Reporter(PrivateMethods)
  135. - (id)initWithConfigFile:(const char *)configFile;
  136. // Returns YES if it has been long enough since the last report that we should
  137. // submit a report for this crash.
  138. - (BOOL)reportIntervalElapsed;
  139. // Returns YES if we should send the report without asking the user first.
  140. - (BOOL)shouldSubmitSilently;
  141. // Returns YES if the minidump was generated on demand.
  142. - (BOOL)isOnDemand;
  143. // Returns YES if we should ask the user to provide comments.
  144. - (BOOL)shouldRequestComments;
  145. // Returns YES if we should ask the user to provide an email address.
  146. - (BOOL)shouldRequestEmail;
  147. // Shows UI to the user to ask for permission to send and any extra information
  148. // we've been instructed to request. Returns YES if the user allows the report
  149. // to be sent.
  150. - (BOOL)askUserPermissionToSend;
  151. // Returns the short description of the crash, suitable for use as a dialog
  152. // title (e.g., "The application Foo has quit unexpectedly").
  153. - (NSString*)shortDialogMessage;
  154. // Return explanatory text about the crash and the reporter, suitable for the
  155. // body text of a dialog.
  156. - (NSString*)explanatoryDialogText;
  157. // Returns the amount of time the UI should be shown before timing out.
  158. - (NSTimeInterval)messageTimeout;
  159. // Preps the comment-prompting alert window for display:
  160. // * localizes all the elements
  161. // * resizes and adjusts layout as necessary for localization
  162. // * removes the email section if includeEmail is NO
  163. - (void)configureAlertWindowIncludingEmail:(BOOL)includeEmail;
  164. // Rmevoes the email section of the dialog, adjusting the rest of the window
  165. // as necessary.
  166. - (void)removeEmailPrompt;
  167. // Run an alert window with the given timeout. Returns
  168. // NSRunStoppedResponse if the timeout is exceeded. A timeout of 0
  169. // queues the message immediately in the modal run loop.
  170. - (NSInteger)runModalWindow:(NSWindow*)window
  171. withTimeout:(NSTimeInterval)timeout;
  172. // This method is used to periodically update the UI with how many
  173. // seconds are left in the dialog display.
  174. - (void)updateSecondsLeftInDialogDisplay:(NSTimer*)theTimer;
  175. // When we receive this notification, it means that the user has
  176. // begun editing the email address or comments field, and we disable
  177. // the timers so that the user has as long as they want to type
  178. // in their comments/email.
  179. - (void)controlTextDidBeginEditing:(NSNotification *)aNotification;
  180. - (void)report;
  181. @end
  182. @implementation Reporter
  183. //=============================================================================
  184. - (id)initWithConfigFile:(const char *)configFile {
  185. if ((self = [super init])) {
  186. remainingDialogTime_ = 0;
  187. uploader_ = [[Uploader alloc] initWithConfigFile:configFile];
  188. if (!uploader_) {
  189. [self release];
  190. return nil;
  191. }
  192. }
  193. return self;
  194. }
  195. //=============================================================================
  196. - (BOOL)askUserPermissionToSend {
  197. // Initialize Cocoa, needed to display the alert
  198. NSApplicationLoad();
  199. // Get the timeout value for the notification.
  200. NSTimeInterval timeout = [self messageTimeout];
  201. NSInteger buttonPressed = NSAlertAlternateReturn;
  202. // Determine whether we should create a text box for user feedback.
  203. if ([self shouldRequestComments]) {
  204. BOOL didLoadNib = [NSBundle loadNibNamed:@"Breakpad" owner:self];
  205. if (!didLoadNib) {
  206. return NO;
  207. }
  208. [self configureAlertWindowIncludingEmail:[self shouldRequestEmail]];
  209. buttonPressed = [self runModalWindow:alertWindow_ withTimeout:timeout];
  210. // Extract info from the user into the uploader_.
  211. if ([self commentsValue]) {
  212. [[uploader_ parameters] setObject:[self commentsValue]
  213. forKey:@BREAKPAD_COMMENTS];
  214. }
  215. if ([self emailValue]) {
  216. [[uploader_ parameters] setObject:[self emailValue]
  217. forKey:@BREAKPAD_EMAIL];
  218. }
  219. } else {
  220. // Create an alert panel to tell the user something happened
  221. NSPanel* alert = NSGetAlertPanel([self shortDialogMessage],
  222. [self explanatoryDialogText],
  223. NSLocalizedString(@"sendReportButton", @""),
  224. NSLocalizedString(@"cancelButton", @""),
  225. nil);
  226. // Pop the alert with an automatic timeout, and wait for the response
  227. buttonPressed = [self runModalWindow:alert withTimeout:timeout];
  228. // Release the panel memory
  229. NSReleaseAlertPanel(alert);
  230. }
  231. return buttonPressed == NSAlertDefaultReturn;
  232. }
  233. - (void)configureAlertWindowIncludingEmail:(BOOL)includeEmail {
  234. // Swap in localized values, making size adjustments to impacted elements as
  235. // we go. Remember that the origin is in the bottom left, so elements above
  236. // "fall" as text areas are shrunk from their overly-large IB sizes.
  237. // Localize the header. No resizing needed, as it has plenty of room.
  238. [dialogTitle_ setStringValue:[self shortDialogMessage]];
  239. // Localize the explanatory text field.
  240. [commentMessage_ setStringValue:[NSString stringWithFormat:@"%@\n\n%@",
  241. [self explanatoryDialogText],
  242. NSLocalizedString(@"commentsMsg", @"")]];
  243. CGFloat commentHeightDelta = [commentMessage_ breakpad_adjustHeightToFit];
  244. [headerBox_ breakpad_shiftVertically:commentHeightDelta];
  245. [alertWindow_ breakpad_adjustHeight:commentHeightDelta];
  246. // Either localize the email explanation field or remove the whole email
  247. // section depending on whether or not we are asking for email.
  248. if (includeEmail) {
  249. [emailMessage_ setStringValue:NSLocalizedString(@"emailMsg", @"")];
  250. CGFloat emailHeightDelta = [emailMessage_ breakpad_adjustHeightToFit];
  251. [preEmailBox_ breakpad_shiftVertically:emailHeightDelta];
  252. [alertWindow_ breakpad_adjustHeight:emailHeightDelta];
  253. } else {
  254. [self removeEmailPrompt]; // Handles necessary resizing.
  255. }
  256. // Localize the email label, and shift the associated text field.
  257. [emailLabel_ setStringValue:NSLocalizedString(@"emailLabel", @"")];
  258. CGFloat emailLabelWidthDelta = [emailLabel_ breakpad_adjustWidthToFit];
  259. [emailEntryField_ breakpad_shiftHorizontally:emailLabelWidthDelta];
  260. // Localize the privacy policy label, and keep it right-aligned to the arrow.
  261. [privacyLinkLabel_ setStringValue:NSLocalizedString(@"privacyLabel", @"")];
  262. CGFloat privacyLabelWidthDelta =
  263. [privacyLinkLabel_ breakpad_adjustWidthToFit];
  264. [privacyLinkLabel_ breakpad_shiftHorizontally:(-privacyLabelWidthDelta)];
  265. // Ensure that the email field and the privacy policy link don't overlap.
  266. CGFloat kMinControlPadding = 8;
  267. CGFloat maxEmailFieldWidth = NSMinX([privacyLinkLabel_ frame]) -
  268. NSMinX([emailEntryField_ frame]) -
  269. kMinControlPadding;
  270. if (NSWidth([emailEntryField_ bounds]) > maxEmailFieldWidth &&
  271. maxEmailFieldWidth > 0) {
  272. NSSize emailSize = [emailEntryField_ frame].size;
  273. emailSize.width = maxEmailFieldWidth;
  274. [emailEntryField_ setFrameSize:emailSize];
  275. }
  276. // Localize the placeholder text.
  277. [[commentsEntryField_ cell]
  278. setPlaceholderString:NSLocalizedString(@"commentsPlaceholder", @"")];
  279. [[emailEntryField_ cell]
  280. setPlaceholderString:NSLocalizedString(@"emailPlaceholder", @"")];
  281. // Localize the buttons, and keep the cancel button at the right distance.
  282. [sendButton_ setTitle:NSLocalizedString(@"sendReportButton", @"")];
  283. CGFloat sendButtonWidthDelta = [sendButton_ breakpad_smartSizeToFit];
  284. [cancelButton_ breakpad_shiftHorizontally:(-sendButtonWidthDelta)];
  285. [cancelButton_ setTitle:NSLocalizedString(@"cancelButton", @"")];
  286. [cancelButton_ breakpad_smartSizeToFit];
  287. }
  288. - (void)removeEmailPrompt {
  289. [emailSectionBox_ setHidden:YES];
  290. CGFloat emailSectionHeight = NSHeight([emailSectionBox_ frame]);
  291. [preEmailBox_ breakpad_shiftVertically:(-emailSectionHeight)];
  292. [alertWindow_ breakpad_adjustHeight:(-emailSectionHeight)];
  293. }
  294. - (NSInteger)runModalWindow:(NSWindow*)window
  295. withTimeout:(NSTimeInterval)timeout {
  296. // Queue a |stopModal| message to be performed in |timeout| seconds.
  297. if (timeout > 0.001) {
  298. remainingDialogTime_ = timeout;
  299. SEL updateSelector = @selector(updateSecondsLeftInDialogDisplay:);
  300. messageTimer_ = [NSTimer scheduledTimerWithTimeInterval:1.0
  301. target:self
  302. selector:updateSelector
  303. userInfo:nil
  304. repeats:YES];
  305. }
  306. // Run the window modally and wait for either a |stopModal| message or a
  307. // button click.
  308. [NSApp activateIgnoringOtherApps:YES];
  309. NSInteger returnMethod = [NSApp runModalForWindow:window];
  310. return returnMethod;
  311. }
  312. - (IBAction)sendReport:(id)sender {
  313. // Force the text fields to end editing so text for the currently focused
  314. // field will be commited.
  315. [alertWindow_ makeFirstResponder:alertWindow_];
  316. [alertWindow_ orderOut:self];
  317. // Use NSAlertDefaultReturn so that the return value of |runModalWithWindow|
  318. // matches the AppKit function NSRunAlertPanel()
  319. [NSApp stopModalWithCode:NSAlertDefaultReturn];
  320. }
  321. // UI Button Actions
  322. //=============================================================================
  323. - (IBAction)cancel:(id)sender {
  324. [alertWindow_ orderOut:self];
  325. // Use NSAlertDefaultReturn so that the return value of |runModalWithWindow|
  326. // matches the AppKit function NSRunAlertPanel()
  327. [NSApp stopModalWithCode:NSAlertAlternateReturn];
  328. }
  329. - (IBAction)showPrivacyPolicy:(id)sender {
  330. // Get the localized privacy policy URL and open it in the default browser.
  331. NSURL* privacyPolicyURL =
  332. [NSURL URLWithString:NSLocalizedString(@"privacyPolicyURL", @"")];
  333. [[NSWorkspace sharedWorkspace] openURL:privacyPolicyURL];
  334. }
  335. // Text Field Delegate Methods
  336. //=============================================================================
  337. - (BOOL) control:(NSControl*)control
  338. textView:(NSTextView*)textView
  339. doCommandBySelector:(SEL)commandSelector {
  340. BOOL result = NO;
  341. // If the user has entered text on the comment field, don't end
  342. // editing on "return".
  343. if (control == commentsEntryField_ &&
  344. commandSelector == @selector(insertNewline:)
  345. && [[textView string] length] > 0) {
  346. [textView insertNewlineIgnoringFieldEditor:self];
  347. result = YES;
  348. }
  349. return result;
  350. }
  351. - (void)controlTextDidBeginEditing:(NSNotification *)aNotification {
  352. [messageTimer_ invalidate];
  353. [self setCountdownMessage:@""];
  354. }
  355. - (void)updateSecondsLeftInDialogDisplay:(NSTimer*)theTimer {
  356. remainingDialogTime_ -= 1;
  357. NSString *countdownMessage;
  358. NSString *formatString;
  359. int displayedTimeLeft; // This can be either minutes or seconds.
  360. if (remainingDialogTime_ > 59) {
  361. // calculate minutes remaining for UI purposes
  362. displayedTimeLeft = (int)(remainingDialogTime_ / 60);
  363. if (displayedTimeLeft == 1) {
  364. formatString = NSLocalizedString(@"countdownMsgMinuteSingular", @"");
  365. } else {
  366. formatString = NSLocalizedString(@"countdownMsgMinutesPlural", @"");
  367. }
  368. } else {
  369. displayedTimeLeft = (int)remainingDialogTime_;
  370. if (displayedTimeLeft == 1) {
  371. formatString = NSLocalizedString(@"countdownMsgSecondSingular", @"");
  372. } else {
  373. formatString = NSLocalizedString(@"countdownMsgSecondsPlural", @"");
  374. }
  375. }
  376. countdownMessage = [NSString stringWithFormat:formatString,
  377. displayedTimeLeft];
  378. if (remainingDialogTime_ <= 30) {
  379. [countdownLabel_ setTextColor:[NSColor redColor]];
  380. }
  381. [self setCountdownMessage:countdownMessage];
  382. if (remainingDialogTime_ <= 0) {
  383. [messageTimer_ invalidate];
  384. [NSApp stopModal];
  385. }
  386. }
  387. #pragma mark Accessors
  388. #pragma mark -
  389. //=============================================================================
  390. - (NSString *)commentsValue {
  391. return [[commentsValue_ retain] autorelease];
  392. }
  393. - (void)setCommentsValue:(NSString *)value {
  394. if (commentsValue_ != value) {
  395. [commentsValue_ release];
  396. commentsValue_ = [value copy];
  397. }
  398. }
  399. - (NSString *)emailValue {
  400. return [[emailValue_ retain] autorelease];
  401. }
  402. - (void)setEmailValue:(NSString *)value {
  403. if (emailValue_ != value) {
  404. [emailValue_ release];
  405. emailValue_ = [value copy];
  406. }
  407. }
  408. - (NSString *)countdownMessage {
  409. return [[countdownMessage_ retain] autorelease];
  410. }
  411. - (void)setCountdownMessage:(NSString *)value {
  412. if (countdownMessage_ != value) {
  413. [countdownMessage_ release];
  414. countdownMessage_ = [value copy];
  415. }
  416. }
  417. #pragma mark -
  418. //=============================================================================
  419. - (BOOL)reportIntervalElapsed {
  420. float interval = [[[uploader_ parameters]
  421. objectForKey:@BREAKPAD_REPORT_INTERVAL] floatValue];
  422. NSString *program = [[uploader_ parameters] objectForKey:@BREAKPAD_PRODUCT];
  423. NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
  424. NSMutableDictionary *programDict =
  425. [NSMutableDictionary dictionaryWithDictionary:[ud dictionaryForKey:program]];
  426. NSNumber *lastTimeNum = [programDict objectForKey:kLastSubmission];
  427. NSTimeInterval lastTime = lastTimeNum ? [lastTimeNum floatValue] : 0;
  428. NSTimeInterval now = CFAbsoluteTimeGetCurrent();
  429. NSTimeInterval spanSeconds = (now - lastTime);
  430. [programDict setObject:[NSNumber numberWithDouble:now]
  431. forKey:kLastSubmission];
  432. [ud setObject:programDict forKey:program];
  433. [ud synchronize];
  434. // If we've specified an interval and we're within that time, don't ask the
  435. // user if we should report
  436. GTMLoggerDebug(@"Reporter Interval: %f", interval);
  437. if (interval > spanSeconds) {
  438. GTMLoggerDebug(@"Within throttling interval, not sending report");
  439. return NO;
  440. }
  441. return YES;
  442. }
  443. - (BOOL)isOnDemand {
  444. return [[[uploader_ parameters] objectForKey:@BREAKPAD_ON_DEMAND]
  445. isEqualToString:@"YES"];
  446. }
  447. - (BOOL)shouldSubmitSilently {
  448. return [[[uploader_ parameters] objectForKey:@BREAKPAD_SKIP_CONFIRM]
  449. isEqualToString:@"YES"];
  450. }
  451. - (BOOL)shouldRequestComments {
  452. return [[[uploader_ parameters] objectForKey:@BREAKPAD_REQUEST_COMMENTS]
  453. isEqualToString:@"YES"];
  454. }
  455. - (BOOL)shouldRequestEmail {
  456. return [[[uploader_ parameters] objectForKey:@BREAKPAD_REQUEST_EMAIL]
  457. isEqualToString:@"YES"];
  458. }
  459. - (NSString*)shortDialogMessage {
  460. NSString *displayName =
  461. [[uploader_ parameters] objectForKey:@BREAKPAD_PRODUCT_DISPLAY];
  462. if (![displayName length])
  463. displayName = [[uploader_ parameters] objectForKey:@BREAKPAD_PRODUCT];
  464. if ([self isOnDemand]) {
  465. return [NSString
  466. stringWithFormat:NSLocalizedString(@"noCrashDialogHeader", @""),
  467. displayName];
  468. } else {
  469. return [NSString
  470. stringWithFormat:NSLocalizedString(@"crashDialogHeader", @""),
  471. displayName];
  472. }
  473. }
  474. - (NSString*)explanatoryDialogText {
  475. NSString *displayName =
  476. [[uploader_ parameters] objectForKey:@BREAKPAD_PRODUCT_DISPLAY];
  477. if (![displayName length])
  478. displayName = [[uploader_ parameters] objectForKey:@BREAKPAD_PRODUCT];
  479. NSString *vendor = [[uploader_ parameters] objectForKey:@BREAKPAD_VENDOR];
  480. if (![vendor length])
  481. vendor = @"unknown vendor";
  482. if ([self isOnDemand]) {
  483. return [NSString
  484. stringWithFormat:NSLocalizedString(@"noCrashDialogMsg", @""),
  485. vendor, displayName];
  486. } else {
  487. return [NSString
  488. stringWithFormat:NSLocalizedString(@"crashDialogMsg", @""),
  489. vendor];
  490. }
  491. }
  492. - (NSTimeInterval)messageTimeout {
  493. // Get the timeout value for the notification.
  494. NSTimeInterval timeout = [[[uploader_ parameters]
  495. objectForKey:@BREAKPAD_CONFIRM_TIMEOUT] floatValue];
  496. // Require a timeout of at least a minute (except 0, which means no timeout).
  497. if (timeout > 0.001 && timeout < 60.0) {
  498. timeout = 60.0;
  499. }
  500. return timeout;
  501. }
  502. - (void)report {
  503. [uploader_ report];
  504. }
  505. //=============================================================================
  506. - (void)dealloc {
  507. [uploader_ release];
  508. [super dealloc];
  509. }
  510. - (void)awakeFromNib {
  511. [emailEntryField_ setMaximumLength:kEmailMaxLength];
  512. [commentsEntryField_ setMaximumLength:kUserCommentsMaxLength];
  513. }
  514. @end
  515. //=============================================================================
  516. @implementation LengthLimitingTextField
  517. - (void)setMaximumLength:(NSUInteger)maxLength {
  518. maximumLength_ = maxLength;
  519. }
  520. // This is the method we're overriding in NSTextField, which lets us
  521. // limit the user's input if it makes the string too long.
  522. - (BOOL) textView:(NSTextView *)textView
  523. shouldChangeTextInRange:(NSRange)affectedCharRange
  524. replacementString:(NSString *)replacementString {
  525. // Sometimes the range comes in invalid, so reject if we can't
  526. // figure out if the replacement text is too long.
  527. if (affectedCharRange.location == NSNotFound) {
  528. return NO;
  529. }
  530. // Figure out what the new string length would be, taking into
  531. // account user selections.
  532. NSUInteger newStringLength =
  533. [[textView string] length] - affectedCharRange.length +
  534. [replacementString length];
  535. if (newStringLength > maximumLength_) {
  536. return NO;
  537. } else {
  538. return YES;
  539. }
  540. }
  541. // Cut, copy, and paste have to be caught specifically since there is no menu.
  542. - (BOOL)performKeyEquivalent:(NSEvent*)event {
  543. // Only handle the key equivalent if |self| is the text field with focus.
  544. NSText* fieldEditor = [self currentEditor];
  545. if (fieldEditor != nil) {
  546. // Check for a single "Command" modifier
  547. NSUInteger modifiers = [event modifierFlags];
  548. modifiers &= NSDeviceIndependentModifierFlagsMask;
  549. if (modifiers == NSCommandKeyMask) {
  550. // Now, check for Select All, Cut, Copy, or Paste key equivalents.
  551. NSString* characters = [event characters];
  552. // Select All is Command-A.
  553. if ([characters isEqualToString:@"a"]) {
  554. [fieldEditor selectAll:self];
  555. return YES;
  556. // Cut is Command-X.
  557. } else if ([characters isEqualToString:@"x"]) {
  558. [fieldEditor cut:self];
  559. return YES;
  560. // Copy is Command-C.
  561. } else if ([characters isEqualToString:@"c"]) {
  562. [fieldEditor copy:self];
  563. return YES;
  564. // Paste is Command-V.
  565. } else if ([characters isEqualToString:@"v"]) {
  566. [fieldEditor paste:self];
  567. return YES;
  568. }
  569. }
  570. }
  571. // Let the super class handle the rest (e.g. Command-Period will cancel).
  572. return [super performKeyEquivalent:event];
  573. }
  574. @end
  575. //=============================================================================
  576. int main(int argc, const char *argv[]) {
  577. NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  578. #if DEBUG
  579. // Log to stderr in debug builds.
  580. [GTMLogger setSharedLogger:[GTMLogger standardLoggerWithStderr]];
  581. #endif
  582. GTMLoggerDebug(@"Reporter Launched, argc=%d", argc);
  583. // The expectation is that there will be one argument which is the path
  584. // to the configuration file
  585. if (argc != 2) {
  586. exit(1);
  587. }
  588. Reporter *reporter = [[Reporter alloc] initWithConfigFile:argv[1]];
  589. if (!reporter) {
  590. GTMLoggerDebug(@"reporter initialization failed");
  591. exit(1);
  592. }
  593. // only submit a report if we have not recently crashed in the past
  594. BOOL shouldSubmitReport = [reporter reportIntervalElapsed];
  595. BOOL okayToSend = NO;
  596. // ask user if we should send
  597. if (shouldSubmitReport) {
  598. if ([reporter shouldSubmitSilently]) {
  599. GTMLoggerDebug(@"Skipping confirmation and sending report");
  600. okayToSend = YES;
  601. } else {
  602. okayToSend = [reporter askUserPermissionToSend];
  603. }
  604. }
  605. // If we're running as root, switch over to nobody
  606. if (getuid() == 0 || geteuid() == 0) {
  607. struct passwd *pw = getpwnam("nobody");
  608. // If we can't get a non-root uid, don't send the report
  609. if (!pw) {
  610. GTMLoggerDebug(@"!pw - %s", strerror(errno));
  611. exit(0);
  612. }
  613. if (setgid(pw->pw_gid) == -1) {
  614. GTMLoggerDebug(@"setgid(pw->pw_gid) == -1 - %s", strerror(errno));
  615. exit(0);
  616. }
  617. if (setuid(pw->pw_uid) == -1) {
  618. GTMLoggerDebug(@"setuid(pw->pw_uid) == -1 - %s", strerror(errno));
  619. exit(0);
  620. }
  621. }
  622. else {
  623. GTMLoggerDebug(@"getuid() !=0 || geteuid() != 0");
  624. }
  625. if (okayToSend && shouldSubmitReport) {
  626. GTMLoggerDebug(@"Sending Report");
  627. [reporter report];
  628. GTMLoggerDebug(@"Report Sent!");
  629. } else {
  630. GTMLoggerDebug(@"Not sending crash report okayToSend=%d, "\
  631. "shouldSubmitReport=%d", okayToSend, shouldSubmitReport);
  632. }
  633. GTMLoggerDebug(@"Exiting with no errors");
  634. // Cleanup
  635. [reporter release];
  636. [pool release];
  637. return 0;
  638. }