Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pcurl command #109

Merged
merged 15 commits into from
Sep 28, 2015
58 changes: 58 additions & 0 deletions commands/FBPrintCommands.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def lldbcommands():
FBPrintApplicationDocumentsPath(),
FBPrintData(),
FBPrintTargetActions(),
FBPrintAsCurl(),
]

class FBPrintViewHierarchyCommand(fb.FBCommand):
Expand Down Expand Up @@ -424,3 +425,60 @@ def run(self, arguments, options):
actionsDescription = fb.evaluateExpressionValue('(id)[{actions} componentsJoinedByString:@", "]'.format(actions=actions)).GetObjectDescription()

print '{target}: {actions}'.format(target=targetDescription, actions=actionsDescription)

class FBPrintAsCurl(fb.FBCommand):
def name(self):
return 'pcurl'

def description(self):
return 'Print the NSURLRequest (HTTP) as curl command.'

def options(self):
return [
fb.FBCommandArgument(short='-p', long='--portable', arg='portable', boolean=True, default=False, help='Embed request data as base64.'),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be useBase64Data or something? portable seems like a strange name for this parameter.

Or, just always do base64 data?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mattjgalloway actually 'portable' in this case means portable command or "you can run this command on other computers, not current one only"
http://dictionary.reference.com/browse/portable

base64 looks ugly and contains overhead on every run, not sure always output it is a good option.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't get how 'portable' makes sense here still. It sort of makes sense, but not intuitive to me.

Since you're copy-pasting the curl command anyway, I don't see why it matters that the base64 is ugly.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my real-life test base64 is longer than one screen

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ok. Big amounts of data!

I still do think that portable is a bad name. Maybe even it should be dataStyle and then options are tmp or base64 and it defaults to the one which makes most sense for the current platform.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Happy to see what @kastiglione / @arigrant think too.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"portable" is definitely opaque to me. I'm good with --base64. Other suggestions: --inline(-data), --embed(-data)

]

def args(self):
return [ fb.FBCommandArgument(arg='request', type='NSURLRequest*/NSMutableURLRequest*', help='The request to convert to the curl command.') ]

def run(self, arguments, options):
request = arguments[0]
HTTPHeaderSring = ''
HTTPMethod = fb.evaluateExpressionValue('(id)[{} HTTPMethod]'.format(request)).GetObjectDescription()
URL = fb.evaluateExpressionValue('(id)[{} URL]'.format(request)).GetObjectDescription()
timeout = fb.evaluateExpression('(NSTimeInterval)[{} timeoutInterval]'.format(request))
HTTPHeaders = fb.evaluateObjectExpression('(id)[{} allHTTPHeaderFields]'.format(request))
HTTPHeadersCount = fb.evaluateIntegerExpression('[{} count]'.format(HTTPHeaders))
allHTTPKeys = fb.evaluateObjectExpression('[{} allKeys]'.format(HTTPHeaders))
for index in range(0, HTTPHeadersCount):
key = fb.evaluateObjectExpression('[{} objectAtIndex:{}]'.format(allHTTPKeys, index))
keyDescription = fb.evaluateExpressionValue('(id){}'.format(key)).GetObjectDescription()
value = fb.evaluateExpressionValue('(id)[(id){} objectForKey:{}]'.format(HTTPHeaders, key)).GetObjectDescription()
if len(HTTPHeaderSring) > 0:
HTTPHeaderSring += ' '
HTTPHeaderSring += '-H "{}: {}"'.format(keyDescription, value)
HTTPData = fb.evaluateObjectExpression('[{} HTTPBody]'.format(request))
dataFile = None
dataAsString = None
if fb.evaluateIntegerExpression('[{} length]'.format(HTTPData)) > 0:
dataFile = '/tmp/curl_data_{}'.format(fb.evaluateExpression('(NSTimeInterval)[NSDate timeIntervalSinceReferenceDate]'))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Move this inside the elif not runtimeHelpers.isIOSDevice():.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this line should be moved into the elif not runtimeHelpers.isIOSDevice(): branch. EDIT: Oops I had "Show notes" turned off, didn't see that I was duplicating @mattjgalloway's comment.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@VTopoliuk actually pointed out that this is required outside of the elif, as it's used down on line 476.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mattjgalloway Ah I see. I didn't know that sandbox apps could write to /tmp. If that works, then sweet!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. Actually, it would be best to set to None here, and then inside the elif not runtimeHelpers.isIOSDevice() set it to the string. That way it's only set if we did actually write it to the file.

if options.portable:
if fb.evaluateIntegerExpression('[{} respondsToSelector:@selector(base64EncodedStringWithOptions:)]'.format(HTTPData)):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably no need to do this, since it's available in iOS 7 and I don't think we support <iOS 7. Although I can't remember for sure. @kastiglione?

And if we do supper <iOS 7 I would prefer in the else case here to print an error, and include an option to skip the data part of this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if we state an official iOS version support. If we don't, this is as good a time to start. So yeah, let's say iOS 7+.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This command works on OS X as well, but this method available only for 10.9+

dataAsString = fb.evaluateExpressionValue('(id)[(id){} base64EncodedStringWithOptions:0]'.format(HTTPData)).GetObjectDescription()
elif not runtimeHelpers.isIOSDevice():
fb.evaluateExpression('(BOOL)[{} writeToFile:@"{}" atomically:NO]'.format(HTTPData, dataFile))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to at least warn if this fails.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, you are totally right!

else:
print 'HTTPBody data for iOS Device is supported only with "--portable" flag'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/--portable/--embed-data/

return False

commandString = ''
if dataAsString is not None and len(dataAsString) > 0:
commandString += 'echo "{}" | base64 -D -o "{}"; '.format(dataAsString, dataFile)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be && instead of ; to prevent curl from trying if the base64 decoding fails.

commandString += 'curl -X {} --connect-timeout {}'.format(HTTPMethod, timeout)
if len(HTTPHeaderSring) > 0:
commandString += ' ' + HTTPHeaderSring
if dataFile is not None:
commandString += ' --data-binary @"{}"'.format(dataFile)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mattjgalloway actually same file are used in two cases:

  1. To embed data
  2. To write data to file

If I will place name generation under the not runtimeHelpers.isIOSDevice(): condition, then it will be not available here with --embed-data option.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh whoops sorry!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we go?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kastiglione do you think it's ready for merge?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left a couple comments. Primarily needs a fix for the location of dataFile.


commandString += ' "{}"'.format(URL)
print commandString
6 changes: 6 additions & 0 deletions fblldbobjcruntimehelpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,9 @@ def isMacintoshArch():
command = '(void*)objc_getClass("{}")'.format(nsClassName)

return (fb.evaluateBooleanExpression(command + '!= nil'))

def isIOSSimulator():
return fb.evaluateExpressionValue('(id)[[UIDevice currentDevice] model]').GetObjectDescription().lower().find('simulator') >= 0

def isIOSDevice():
return not isMacintoshArch() and not isIOSSimulator()