adium/adium
Clone
Summary
Browse
Changes
Graph
Prompt the user to authenticate again when using the refresh_token gives an error. Also, show the error to the user.
GoogleOAuth2
2015-04-01, Thijs Alkemade
c4923ddd82bf
Prompt the user to authenticate again when using the refresh_token gives an error. Also, show the error to the user.
/*
* Adium is the legal property of its developers, whose names are listed in the copyright file included
* with this source distribution.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the GNU
* General Public License as published by the Free Software Foundation; either version 2 of the License,
* or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program; if not,
* write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#import "adiumPurpleRequest.h"
#import "ESPurpleRequestActionController.h"
#import "ESPurpleRequestWindowController.h"
#import "ESPurpleFileReceiveRequestController.h"
#import "AILibpurplePlugin.h"
#import "AMPurpleRequestFieldsController.h"
#import <Adium/AIAccountControllerProtocol.h>
#import <Adium/AIContactAlertsControllerProtocol.h>
#import <Adium/ESFileTransfer.h>
#import <Adium/AIListContact.h>
#import <AIUtilities/AIObjectAdditions.h>
#import <AdiumLibpurple/SLPurpleCocoaAdapter.h>
#import <libintl/libintl.h>
/*
* Purple requires us to return a handle from each of the request functions. This handle is passed back to use in
* adiumPurpleRequestClose() if the request window is no longer valid -- for example, a chat invitation window is open,
* and then the account disconnects. All window controllers created from adiumPurpleRequest.m should return non-autoreleased
* instances of themselves. They then release themselves when their window closes. Rather than calling
* [[self window] close], they should use purple_request_close_with_handle(self) to ensure proper bookkeeping purpleside.
*/
//Jabber registration
#import <libpurple/jabber.h>
/* resolved id for Meanwhile */
struct
resolved_id
{
char
*
id
;
char
*
name
;
};
/*!
* @brief Process button text, removing gtk+ accelerator underscores
*
* Textual underscores are indicated by "__"
*/
NSString
*
processButtonText
(
NSString
*
inButtonText
)
{
NSMutableString
*
processedText
=
[
inButtonText
mutableCopy
];
#define UNDERSCORE_PLACEHOLDER @"&&&&&"
//Replace escaped underscores with our placeholder
[
processedText
replaceOccurrencesOfString
:
@"__"
withString
:
UNDERSCORE_PLACEHOLDER
options
:
NSLiteralSearch
range
:
NSMakeRange
(
0
,
[
processedText
length
])];
//Remove solitary underscores
[
processedText
replaceOccurrencesOfString
:
@"_"
withString
:
@""
options
:
NSLiteralSearch
range
:
NSMakeRange
(
0
,
[
processedText
length
])];
//Replace the placeholder with an underscore
[
processedText
replaceOccurrencesOfString
:
UNDERSCORE_PLACEHOLDER
withString
:
@"_"
options
:
NSLiteralSearch
range
:
NSMakeRange
(
0
,
[
processedText
length
])];
return
[
processedText
autorelease
];
}
static
void
*
adiumPurpleRequestInput
(
const
char
*
title
,
const
char
*
primary
,
const
char
*
secondary
,
const
char
*
defaultValue
,
gboolean
multiline
,
gboolean
masked
,
gchar
*
hint
,
const
char
*
okText
,
GCallback
okCb
,
const
char
*
cancelText
,
GCallback
cancelCb
,
PurpleAccount
*
account
,
const
char
*
who
,
PurpleConversation
*
conv
,
void
*
userData
)
{
NSAutoreleasePool
*
pool
=
[[
NSAutoreleasePool
alloc
]
init
];
/*
Multiline should be a paragraph-sized box; otherwise, a single line will suffice.
Masked means we want to use an NSSecureTextField sort of thing.
We may receive any combination of primary and secondary text (either, both, or neither).
*/
id
requestController
=
nil
;
NSString
*
primaryString
=
(
primary
?
[
NSString
stringWithUTF8String
:
primary
]
:
nil
);
//Ignore purple trying to get an account's password; we'll feed it the password and reconnect if it gets here, somehow.
if
(
primaryString
&&
[
primaryString
rangeOfString
:
@"Enter password for "
].
location
!=
NSNotFound
)
return
[
NSNull
null
];
NSMutableDictionary
*
infoDict
;
NSString
*
okButtonText
=
processButtonText
([
NSString
stringWithUTF8String
:
okText
]);
NSString
*
cancelButtonText
=
processButtonText
([
NSString
stringWithUTF8String
:
cancelText
]);
infoDict
=
[
NSMutableDictionary
dictionaryWithObjectsAndKeys
:
okButtonText
,
@"OK Text"
,
cancelButtonText
,
@"Cancel Text"
,
[
NSValue
valueWithPointer
:
okCb
],
@"OK Callback"
,
[
NSValue
valueWithPointer
:
cancelCb
],
@"Cancel Callback"
,
[
NSValue
valueWithPointer
:
userData
],
@"userData"
,
nil
];
if
(
primaryString
)
[
infoDict
setObject
:
primaryString
forKey
:
@"Primary Text"
];
if
(
title
)
[
infoDict
setObject
:
[
NSString
stringWithUTF8String
:
title
]
forKey
:
@"Title"
];
if
(
defaultValue
)
[
infoDict
setObject
:
[
NSString
stringWithUTF8String
:
defaultValue
]
forKey
:
@"Default Value"
];
if
(
secondary
)
[
infoDict
setObject
:
[
NSString
stringWithUTF8String
:
secondary
]
forKey
:
@"Secondary Text"
];
[
infoDict
setObject
:
[
NSNumber
numberWithBool
:
multiline
]
forKey
:
@"Multiline"
];
[
infoDict
setObject
:
[
NSNumber
numberWithBool
:
masked
]
forKey
:
@"Masked"
];
AILogWithSignature
(
@"%@"
,
infoDict
);
requestController
=
[
ESPurpleRequestWindowController
showInputWindowWithDict
:
infoDict
];
[
pool
drain
];
return
requestController
;
}
static
void
*
adiumPurpleRequestChoice
(
const
char
*
title
,
const
char
*
primary
,
const
char
*
secondary
,
gint
defaultValue
,
const
char
*
okText
,
GCallback
okCb
,
const
char
*
cancelText
,
GCallback
cancelCb
,
PurpleAccount
*
account
,
const
char
*
who
,
PurpleConversation
*
conv
,
void
*
userData
,
va_list
choices
)
{
AILogWithSignature
(
@"%s
\n
%s
\n
%s "
,
(
title
?
title
:
""
),
(
primary
?
primary
:
""
),
(
secondary
?
secondary
:
""
));
return
nil
;
}
static
void
*
adiumPurpleRequestActionWithIcon
(
const
char
*
title
,
const
char
*
primary
,
const
char
*
secondary
,
int
default_action
,
PurpleAccount
*
account
,
const
char
*
who
,
PurpleConversation
*
conv
,
gconstpointer
icon_data
,
gsize
icon_size
,
void
*
userData
,
size_t
actionCount
,
va_list
actions
)
{
NSAutoreleasePool
*
pool
=
[[
NSAutoreleasePool
alloc
]
init
];
NSString
*
titleString
=
(
title
?
[
NSString
stringWithUTF8String
:
title
]
:
@""
);
NSString
*
primaryString
=
(
primary
?
[
NSString
stringWithUTF8String
:
primary
]
:
nil
);
id
requestController
=
nil
;
NSInteger
i
;
BOOL
handled
=
NO
;
if
(
primaryString
&&
([
primaryString
isEqualToString
:
[
NSString
stringWithFormat
:
[
NSString
stringWithUTF8String
:
_
(
"%s has just asked to directly connect to %s"
)],
who
,
purple_account_get_username
(
account
)]]))
{
AIListContact
*
adiumContact
=
contactLookupFromBuddy
(
purple_find_buddy
(
account
,
who
));
// Look up the user preference for this setting -- we use the same settings as the File Transfer code.
AIFileTransferAutoAcceptType
autoAccept
=
[[
adium
.
preferenceController
preferenceForKey
:
KEY_FT_AUTO_ACCEPT
group
:
PREF_GROUP_FILE_TRANSFER
]
intValue
];
if
((
autoAccept
==
AutoAccept_All
)
||
((
autoAccept
==
AutoAccept_FromContactList
)
&&
adiumContact
&&
[
adiumContact
isIntentionallyNotAStranger
]))
{
GCallback
ok_cb
;
//Get the callback for Connect, skipping over the title
va_arg
(
actions
,
char
*
);
ok_cb
=
va_arg
(
actions
,
GCallback
);
((
PurpleRequestActionCb
)
ok_cb
)(
userData
,
default_action
);
handled
=
YES
;
}
}
else
if
(
primary
&&
strcmp
(
primary
,
_
(
"Accept chat invitation?"
))
==
0
)
{
AIListContact
*
contact
=
contactLookupFromBuddy
(
purple_find_buddy
(
account
,
who
));
[
adium
.
contactAlertsController
generateEvent
:
CONTENT_GROUP_CHAT_INVITE
forListObject
:
contact
userInfo
:[
NSDictionary
dictionary
]
previouslyPerformedActionIDs
:
nil
];
}
if
(
!
handled
)
{
NSString
*
secondaryString
=
(
secondary
?
[
NSString
stringWithUTF8String
:
secondary
]
:
nil
);
NSMutableArray
*
buttonNamesArray
=
[
NSMutableArray
arrayWithCapacity
:
actionCount
];
GCallback
*
callBacks
=
g_new0
(
GCallback
,
actionCount
);
//Generate the actions names and callbacks into useable forms
for
(
i
=
0
;
i
<
actionCount
;
i
+=
1
)
{
char
*
buttonName
;
//Get the name
buttonName
=
va_arg
(
actions
,
char
*
);
[
buttonNamesArray
addObject
:
processButtonText
([
NSString
stringWithUTF8String
:
buttonName
])];
//Get the callback for that name
callBacks
[
i
]
=
va_arg
(
actions
,
GCallback
);
}
//Make default_action (or first if none specified) the last one
if
(
default_action
<
(
NSInteger
)
actionCount
-1
)
{
// If there's no default_action, assume the first one is, and move it to the end.
if
(
default_action
==
-1
)
default_action
=
0
;
GCallback
tempCallBack
=
callBacks
[
actionCount
-1
];
callBacks
[
actionCount
-1
]
=
callBacks
[
default_action
];
callBacks
[
default_action
]
=
tempCallBack
;
[
buttonNamesArray
exchangeObjectAtIndex
:
default_action
withObjectAtIndex
:
(
actionCount
-1
)];
}
NSMutableDictionary
*
infoDict
=
[
NSMutableDictionary
dictionaryWithObjectsAndKeys
:
buttonNamesArray
,
@"Button Names"
,
[
NSValue
valueWithPointer
:
callBacks
],
@"callBacks"
,
[
NSValue
valueWithPointer
:
userData
],
@"userData"
,
titleString
,
@"TitleString"
,
nil
];
// If we have both a primary and secondary string, use the primary as a header.
if
(
secondaryString
)
{
[
infoDict
setObject
:
primaryString
forKey
:
@"MessageHeader"
];
[
infoDict
setObject
:
secondaryString
forKey
:
@"Message"
];
}
else
{
[
infoDict
setObject
:
primaryString
forKey
:
@"Message"
];
}
AIAccount
*
adiumAccount
=
accountLookup
(
account
);
if
(
adiumAccount
)
{
[
infoDict
setObject
:
adiumAccount
forKey
:
@"AIAccount"
];
}
if
(
who
)
{
AIListContact
*
adiumContact
=
contactLookupFromBuddy
(
purple_find_buddy
(
account
,
who
));
if
(
adiumContact
)
{
[
infoDict
setObject
:
adiumContact
forKey
:
@"AIListContact"
];
}
[
infoDict
setObject
:
[
NSString
stringWithUTF8String
:
who
]
forKey
:
@"who"
];
}
if
(
icon_data
&&
(
icon_size
>
0
))
{
NSData
*
imageData
=
[
NSData
dataWithBytes
:
icon_data
length
:
icon_size
];
NSImage
*
image
=
[[[
NSImage
alloc
]
initWithData
:
imageData
]
autorelease
];
if
(
image
)
[
infoDict
setObject
:
image
forKey
:
@"Image"
];
}
requestController
=
[
ESPurpleRequestActionController
showActionWindowWithDict
:
infoDict
];
}
[
pool
drain
];
return
requestController
;
}
//Purple requests the user take an action such as accept or deny a buddy's attempt to add us to her list
static
void
*
adiumPurpleRequestAction
(
const
char
*
title
,
const
char
*
primary
,
const
char
*
secondary
,
int
default_action
,
PurpleAccount
*
account
,
const
char
*
who
,
PurpleConversation
*
conv
,
void
*
userData
,
size_t
actionCount
,
va_list
actions
)
{
return
adiumPurpleRequestActionWithIcon
(
title
,
primary
,
secondary
,
default_action
,
account
,
who
,
conv
,
/* iconData */
NULL
,
/* iconSize */
0
,
userData
,
actionCount
,
actions
);
}
static
void
*
adiumPurpleRequestFields
(
const
char
*
title
,
const
char
*
primary
,
const
char
*
secondary
,
PurpleRequestFields
*
fields
,
const
char
*
okText
,
GCallback
okCb
,
const
char
*
cancelText
,
GCallback
cancelCb
,
PurpleAccount
*
account
,
const
char
*
who
,
PurpleConversation
*
conv
,
void
*
userData
)
{
NSAutoreleasePool
*
pool
=
[[
NSAutoreleasePool
alloc
]
init
];
id
requestController
=
nil
;
if
(
title
&&
(
strcmp
(
title
,
_
(
"Register New XMPP Account"
))
==
0
))
{
/* Jabber registration request. Instead of displaying a request dialogue, we fill in the information automatically.
* And by that, I mean that we accept all the default empty values, since the username and password are preset for us. */
((
PurpleRequestFieldsCb
)
okCb
)(
userData
,
fields
);
}
else
if
(
purple_request_fields_get_field
(
fields
,
"password"
)
&&
purple_request_fields_get_field
(
fields
,
"remember"
))
{
/* Password request dialogue. Handle it with our own password request system, which is much prettier. */
AIAccount
*
adiumAccount
=
accountLookup
(
account
);
[
adium
.
accountController
passwordForAccount
:
adiumAccount
promptOption
:
AIPromptAlways
notifyingTarget
:[
ESPurpleRequestAdapter
class
]
selector
:
@selector
(
returnedPassword
:
returnCode
:
context
:
)
context
:[
NSDictionary
dictionaryWithObjectsAndKeys
:
adiumAccount
,
@"Account"
,
[
NSValue
valueWithPointer
:
fields
],
@"fields"
,
[
NSValue
valueWithPointer
:
okCb
],
@"okCb"
,
[
NSValue
valueWithPointer
:
cancelCb
],
@"cancelCb"
,
[
NSValue
valueWithPointer
:
userData
],
@"userData"
,
nil
]];
}
else
{
AILog
(
@"adiumPurpleRequestFields: %s
\n
%s
\n
%s "
,
(
title
?
title
:
""
),
(
primary
?
primary
:
""
),
(
secondary
?
secondary
:
""
));
id
self
=
(
CBPurpleAccount
*
)
account
->
ui_data
;
// for AILocalizedString
requestController
=
[[
AMPurpleRequestFieldsController
alloc
]
initWithTitle
:
(
title
?
[
NSString
stringWithUTF8String
:
title
]
:
nil
)
primaryText
:(
primary
?
[
NSString
stringWithUTF8String
:
primary
]
:
nil
)
secondaryText
:(
secondary
?
[
NSString
stringWithUTF8String
:
secondary
]
:
nil
)
requestFields
:
fields
okText
:(
okText
?
[
NSString
stringWithUTF8String
:
okText
]
:
AILocalizedString
(
@"OK"
,
nil
))
callback
:
okCb
cancelText
:(
cancelText
?
[
NSString
stringWithUTF8String
:
cancelText
]
:
AILocalizedString
(
@"Cancel"
,
nil
))
callback
:
cancelCb
account
:(
CBPurpleAccount
*
)
account
->
ui_data
who
:(
who
?
[
NSString
stringWithUTF8String
:
who
]
:
nil
)
conversation
:
conv
userData
:
userData
];
}
[
pool
drain
];
return
requestController
;
}
static
void
*
adiumPurpleRequestFile
(
const
char
*
title
,
const
char
*
filename
,
gboolean
savedialog
,
GCallback
ok_cb
,
GCallback
cancel_cb
,
PurpleAccount
*
account
,
const
char
*
who
,
PurpleConversation
*
conv
,
void
*
user_data
)
{
NSAutoreleasePool
*
pool
=
[[
NSAutoreleasePool
alloc
]
init
];
if
(
title
)
{
NSString
*
titleString
=
(
title
?
[
NSString
stringWithUTF8String
:
title
]
:
nil
);
if
(
savedialog
)
{
NSSavePanel
*
savePanel
=
[
NSSavePanel
savePanel
];
if
([
titleString
length
])
[
savePanel
setTitle
:
titleString
];
[
savePanel
setAllowedFileTypes
:
nil
];
if
([
savePanel
runModal
]
==
NSOKButton
)
{
((
PurpleRequestFileCb
)
ok_cb
)(
user_data
,
[[[
savePanel
URL
]
path
]
UTF8String
]);
}
}
else
{
NSOpenPanel
*
openPanel
=
[
NSOpenPanel
openPanel
];
if
([
titleString
length
])
[
openPanel
setTitle
:
titleString
];
[
openPanel
setAllowedFileTypes
:
nil
];
if
([
openPanel
runModal
]
==
NSOKButton
)
{
((
PurpleRequestFileCb
)
ok_cb
)(
user_data
,
[[[
openPanel
URL
]
path
]
UTF8String
]);
}
}
}
else
{
/* Only file transfer file requests lack a title */
PurpleXfer
*
xfer
=
(
PurpleXfer
*
)
user_data
;
if
(
xfer
)
{
PurpleXferType
xferType
=
purple_xfer_get_type
(
xfer
);
if
(
xferType
==
PURPLE_XFER_RECEIVE
)
{
AILog
(
@"*** WARNING: File request: %s from %s on IP %s which wasn't handled by the file-recv-request signal"
,
xfer
->
filename
,
xfer
->
who
,
purple_xfer_get_remote_ip
(
xfer
));
/* We should never get here. The file-recv-request signal is posted before we could. We handle that signal
* (in adiumPurpleSignals) and set a local filename when we do to prevent being prompted via the request_file() ui op.
*/
}
else
if
(
xferType
==
PURPLE_XFER_SEND
)
{
/*
* Um, yes, we've already set the local filename... which should be the same as the file name for the transfer itself...
* and we do, in fact, want to send. Call the OK callback immediately.
*/
if
(
xfer
->
local_filename
!=
NULL
&&
xfer
->
filename
!=
NULL
)
{
AILog
(
@"PURPLE_XFER_SEND: %x (%s)"
,
xfer
,
xfer
->
local_filename
);
((
PurpleRequestFileCb
)
ok_cb
)(
user_data
,
xfer
->
local_filename
);
}
else
{
((
PurpleRequestFileCb
)
cancel_cb
)(
user_data
,
xfer
->
local_filename
);
[[
SLPurpleCocoaAdapter
sharedInstance
]
displayFileSendError
];
}
}
}
}
[
pool
drain
];
return
NULL
;
}
/*!
* @brief Purple requests that we close a request window
*
* This is not sent after user interaction with the window. Instead, it is sent when the window is no longer valid;
* for example, a chat invite window after the relevant account disconnects. We should immediately close the window.
*
* @param type The request type
* @param uiHandle must be an id; it should either be NSNull or an object which can respond to close, such as NSWindowController.
*/
static
void
adiumPurpleRequestClose
(
PurpleRequestType
type
,
void
*
uiHandle
)
{
NSAutoreleasePool
*
pool
=
[[
NSAutoreleasePool
alloc
]
init
];
id
ourHandle
=
(
id
)
uiHandle
;
AILogWithSignature
(
@"%@ (%i)"
,
uiHandle
,[
ourHandle
respondsToSelector
:
@selector
(
purpleRequestClose
)]);
if
([
ourHandle
respondsToSelector
:
@selector
(
purpleRequestClose
)])
{
[
ourHandle
purpleRequestClose
];
}
else
if
([
ourHandle
respondsToSelector
:
@selector
(
closeWindow
:
)])
{
[
ourHandle
closeWindow
:
nil
];
}
[
pool
drain
];
}
static
void
*
adiumPurpleRequestFolder
(
const
char
*
title
,
const
char
*
dirname
,
GCallback
ok_cb
,
GCallback
cancel_cb
,
PurpleAccount
*
account
,
const
char
*
who
,
PurpleConversation
*
conv
,
void
*
user_data
)
{
AILogWithSignature
(
@""
);
return
NULL
;
}
static
PurpleRequestUiOps
adiumPurpleRequestOps
=
{
adiumPurpleRequestInput
,
adiumPurpleRequestChoice
,
adiumPurpleRequestAction
,
adiumPurpleRequestFields
,
adiumPurpleRequestFile
,
adiumPurpleRequestClose
,
adiumPurpleRequestFolder
,
adiumPurpleRequestActionWithIcon
,
NULL
,
/* reserved */
NULL
,
/* reserved */
NULL
/* reserved */
};
PurpleRequestUiOps
*
adium_purple_request_get_ui_ops
()
{
return
&
adiumPurpleRequestOps
;
}
@implementation
ESPurpleRequestAdapter
+
(
void
)
requestCloseWithHandle:
(
id
)
handle
{
AILogWithSignature
(
@"%@"
,
handle
);
purple_request_close_with_handle
(
handle
);
}
+
(
void
)
returnedPassword:
(
NSString
*
)
password
returnCode:
(
AIPasswordPromptReturn
)
returnCode
context:
(
id
)
context
{
NSDictionary
*
dict
=
(
NSDictionary
*
)
context
;
PurpleRequestFields
*
fields
=
[[
dict
objectForKey
:
@"fields"
]
pointerValue
];
void
*
userData
=
[[
dict
objectForKey
:
@"userData"
]
pointerValue
];
PurpleRequestFieldsCb
cb
=
NULL
;
switch
(
returnCode
)
{
case
AIPasswordPromptOKReturn
:
{
cb
=
[[
dict
objectForKey
:
@"okCb"
]
pointerValue
];
/* Set the password within the fields structure so libpurple can retrieve it */
PurpleRequestField
*
field
=
purple_request_fields_get_field
(
fields
,
"password"
);
purple_request_field_string_set_value
(
field
,
[
password
UTF8String
]);
break
;
}
case
AIPasswordPromptCancelReturn
:
cb
=
[[
dict
objectForKey
:
@"cancelCb"
]
pointerValue
];
break
;
}
if
(
cb
)
cb
(
userData
,
fields
);
}
@end