2009-08-28 14 views
10

tengo que tomar una cabecera de la siguiente manera:analizar un encabezado de solicitud HTTP Authorization con Python

Authorization: Digest qop="chap", 
    realm="[email protected]", 
    username="Foobear", 
    response="6629fae49393a05397450978507c4ef1", 
    cnonce="5ccc069c403ebaf9f0171e9517f40e41" 

Y analizar en esta usando Python:

{'protocol':'Digest', 
    'qop':'chap', 
    'realm':'[email protected]', 
    'username':'Foobear', 
    'response':'6629fae49393a05397450978507c4ef1', 
    'cnonce':'5ccc069c403ebaf9f0171e9517f40e41'} 

¿Hay una biblioteca para hacer esto, o algo que podría mirar en busca de inspiración?

que esté haciendo esto en Google App Engine, y no estoy seguro de si la biblioteca Pyparsing está disponible, pero tal vez podría incluirlo en mi aplicación si es la mejor solución.

Actualmente estoy creando mi propio objeto MyHeaderParser y usarlo con reduce() en la cadena de encabezado. Está funcionando, pero es muy frágil.

brillante solución de Nadia a continuación:

import re 

reg = re.compile('(\w+)[=] ?"?(\w+)"?') 

s = """Digest 
realm="stackoverflow.com", username="kixx" 
""" 

print str(dict(reg.findall(s))) 
+0

Hasta ahora, esto solución ha s probado solo para ser súper limpio, pero también muy robusto. Si bien no es la implementación más "por libro" del RFC, aún no he creado un caso de prueba que devuelva valores no válidos. Sin embargo, estoy _only_ usando esto para analizar el encabezado Authorization, no necesito analizar los otros encabezados que me interesan, por lo que esta puede no ser una buena solución como un analizador de encabezado HTTP general. –

Respuesta

12

un poco de expresiones regulares:

import re 
reg=re.compile('(\w+)[:=] ?"?(\w+)"?') 

>>>dict(reg.findall(headers)) 

{'username': 'Foobear', 'realm': 'testrealm', 'qop': 'chap', 'cnonce': '5ccc069c403ebaf9f0171e9517f40e41', 'response': '6629fae49393a05397450978507c4ef1', 'Authorization': 'Digest'} 
+0

Guau, me encanta Python. "Autorización:" no es realmente parte de la cadena del encabezado, así que lo hice en su lugar: #!/Usr/bin/python env import re def mymain(): reg = re.compile ('(\ w +) [=] "(\ w +)?"??') s = "" "Digesto realm = "fireworksproject.com", nombre de usuario = "kristoffer" """ str impresión (dict (reg.findall (s))) si __name__ == '__main__': mymain() I No recibo la declaración del protocolo "Resumen", pero de todos modos no la necesito. Esencialmente 3 líneas de código ... Brillante !!! –

+0

Creo que sería más explícito usar una cadena sin formato o \\. –

+0

Si encuentra esto y lo usa, asegúrese de agregar otro signo de interrogación dentro de '"? (\ W +) "' para que se convierta en '"? ​​(\ W +)? "' De esta manera si pasa algo como "" devuelve el parámetro y el valor no está definido. Y si realmente quiere Digest: '/ (\ w +) (?: ([: =])?"? (\ W +)? "?)? /' Verifique si '=' existe en la coincidencia, si es así es una clave: valor de lo contrario es otra cosa. – Nijikokun

1

Si esos componentes siempre estarán ahí, a continuación, una expresión regular va a hacer el truco:

test = '''Authorization: Digest qop="chap", realm="[email protected]", username="Foobear", response="6629fae49393a05397450978507c4ef1", cnonce="5ccc069c403ebaf9f0171e9517f40e41"''' 

import re 

re_auth = re.compile(r""" 
    Authorization:\s*(?P<protocol>[^ ]+)\s+ 
    qop="(?P<qop>[^"]+)",\s+ 
    realm="(?P<realm>[^"]+)",\s+ 
    username="(?P<username>[^"]+)",\s+ 
    response="(?P<response>[^"]+)",\s+ 
    cnonce="(?P<cnonce>[^"]+)" 
    """, re.VERBOSE) 

m = re_auth.match(test) 
print m.groupdict() 

produce:

{ 'username': 'Foobear', 
    'protocol': 'Digest', 
    'qop': 'chap', 
    'cnonce': '5ccc069c403ebaf9f0171e9517f40e41', 
    'realm': '[email protected]', 
    'response': '6629fae49393a05397450978507c4ef1' 
} 
+0

Esta solución produce resultados correctos por lo que he podido ver. –

1

I recomendaría encontrar una biblioteca correcta para analizar los encabezados http. Desafortunadamente no puedo recuperar ninguno. :(

Durante un tiempo, comprobar el siguiente fragmento de código (en su mayoría debe trabajar):

input= """ 
Authorization: Digest qop="chap", 
    realm="[email protected]", 
    username="Foob,ear", 
    response="6629fae49393a05397450978507c4ef1", 
    cnonce="5ccc069c403ebaf9f0171e9517f40e41" 
""" 

field, sep, value = input.partition(":") 
if field.endswith('Authorization'): 
    protocol, sep, opts_str = value.strip().partition(" ") 

    opts = {} 
    for opt in opts_str.split(",\n"): 
     key, value = opt.strip().split('=') 
     key = key.strip(" ") 
     value = value.strip(' "') 
     opts[key] = value 

    opts['protocol'] = protocol 

    print opts 
0

Si su respuesta viene en una única cadena que que no varía y tiene tantas líneas como hay expresiones a partido, se puede dividir en una matriz sobre las nuevas líneas llama authentication_array y utilizar las expresiones regulares:

pattern_array = ['qop', 'realm', 'username', 'response', 'cnonce'] 
i = 0 
parsed_dict = {} 

for line in authentication_array: 
    pattern = "(" + pattern_array[i] + ")" + "=(\".*\")" # build a matching pattern 
    match = re.search(re.compile(pattern), line)   # make the match 
    if match: 
     parsed_dict[match.group(1)] = match.group(2) 
    i += 1 
1

Su concepto original de utilizar PyParsing sería el mejor enfoque. Lo que has pedido implícitamente es algo que requiere una gramática ... es decir, una expresión regular o rutina de análisis sencilla siempre va a ser frágiles, y que suena como que es algo que estamos tratando de evitar.

Parece que conseguir pyparsing en el motor de aplicación de Google es fácil: How do I get PyParsing set up on the Google App Engine?

Así que me gustaría ir con eso, y luego poner en práctica el apoyo cabecera HTTP de autenticación/autorización completa de RFC2617.

+0

Decidí seguir este enfoque y traté de implementar un analizador totalmente compatible para el encabezado Authorization usando la especificación RFC. Esta tarea parece ser mucho más desalentadora. La elección de la expresión regular simple, aunque no rigurosamente correcta, es probablemente la mejor solución pragmática. Informaré aquí si eventualmente obtengo un analizador de encabezado completamente funcional. –

+1

Sí, sería agradable verlo algo más rigurosamente correcto. –

9

También se puede utilizar como urllib2 CheryPy hace.

aquí es el fragmento:

input= """ 
Authorization: Digest qop="chap", 
    realm="[email protected]", 
    username="Foobear", 
    response="6629fae49393a05397450978507c4ef1", 
    cnonce="5ccc069c403ebaf9f0171e9517f40e41" 
""" 
import urllib2 
field, sep, value = input.partition("Authorization: Digest ") 
if value: 
    items = urllib2.parse_http_list(value) 
    opts = urllib2.parse_keqv_list(items) 
    opts['protocol'] = 'Digest' 
    print opts 

da salida:

{'username': 'Foobear', 'protocol': 'Digest', 'qop': 'chap', 'cnonce': '5ccc069c403ebaf9f0171e9517f40e41', 'realm': '[email protected]', 'response': '6629fae49393a05397450978507c4ef1'} 
2

Aquí está mi intento pyparsing:

text = """Authorization: Digest qop="chap", 
    realm="[email protected]",  
    username="Foobear",  
    response="6629fae49393a05397450978507c4ef1",  
    cnonce="5ccc069c403ebaf9f0171e9517f40e41" """ 

from pyparsing import * 

AUTH = Keyword("Authorization") 
ident = Word(alphas,alphanums) 
EQ = Suppress("=") 
quotedString.setParseAction(removeQuotes) 

valueDict = Dict(delimitedList(Group(ident + EQ + quotedString))) 
authentry = AUTH + ":" + ident("protocol") + valueDict 

print authentry.parseString(text).dump() 

que imprime:

['Authorization', ':', 'Digest', ['qop', 'chap'], ['realm', '[email protected]'], 
['username', 'Foobear'], ['response', '6629fae49393a05397450978507c4ef1'], 
['cnonce', '5ccc069c403ebaf9f0171e9517f40e41']] 
- cnonce: 5ccc069c403ebaf9f0171e9517f40e41 
- protocol: Digest 
- qop: chap 
- realm: [email protected] 
- response: 6629fae49393a05397450978507c4ef1 
- username: Foobear 

No estoy familiarizado con el RFC, pero espero que esto lo haga funcionar.

+0

Esta solución es el uso de pypars en lo que originalmente estaba pensando, y, por lo que puedo ver, produce buenos resultados. –

1

El campo de encabezado Autorización de resumen http es un poco extraño. Su formato es similar al de los campos de encabezado Cache-Control y Content-Type de rfc 2616, pero lo suficientemente diferente como para ser incompatible. Si aún busca una biblioteca que sea un poco más inteligente y legible que la expresión regular, puede intentar eliminar la parte de Autorización: Resumen con str.split() y analizar el resto con parse_dict_header() del módulo http Werkzeug. (Werkzeug puede instalarse en App Engine.)

+0

Muchas gracias. Puedo reemplazar esa expresión regular con esto. Parece más robusto. –

1

La expresión regular de Nadia solo coincide con los caracteres alfanuméricos para el valor de un parámetro. Eso significa que no puede analizar al menos dos campos. A saber, el uri y qop. De acuerdo con RFC 2617, el campo uri es un duplicado de la cadena en la línea de solicitud (es decir, la primera línea de la solicitud HTTP). Y qop no puede analizar correctamente si el valor es "auth-int" debido a que no es alfanumérico '-'.

Esta expresión regular modificada permite que el URI (o cualquier otro valor) contenga cualquier cosa que no sea '' (espacio), '"' (qoute) o ',' (coma). Probablemente sea más permisivo de lo necesario. , pero no debe causar ningún problema con correctamente peticiones HTTP formados

reg re.compile('(\w+)[:=] ?"?([^" ,]+)"?') 

consejo adicional:.. a partir de ahí, es bastante sencillo para convertir el código de ejemplo en el RFC-2617 a Python usando API MD5 del pitón, "MD5Init()" se convierte en "m = md5.new()", "MD5Update()" pasa a ser "m.update()" y "MD5Final()" pasa a ser "m.digest()".

Cuestiones relacionadas