-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathRealHTTP.py
177 lines (153 loc) · 7.31 KB
/
RealHTTP.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
#!/usr/bin/python
# Filename: RealHTTP.py
class RealHTTP:
def __init__(self):
# Return values for how the parsing went
self.PARTIAL = 1
self.FULL = 2
self.FAIL = 4
# ~~~~~~~~~~~~~~
self.content = '' # The remainder data after the data is parsed
self.method_type = None
self.url = None
self.version = None
self.headers = {} # A dict of all the headers; key: header, value: whatever the header's value is
self.body = '' # Entity body of the request; if there is one
self.needed_body = 0 # The number of bytes still needed for the entity body
self.error = None # If there parsing fails for any reason a string is put in to explain why
self.start = 1 # This is an important value, it's used for proper formatting of the headers and avoiding overriding correct data; if it has already done the first line of the request and tried to do it again it will skip that header.
def clear(self):
'''Resets all data members to what they would be at time of initialization.'''
self.content = ''
self.method_type = None
self.url = None
self.version = None
self.headers.clear()
self.body = ''
self.needed_body = 0
self.error = None
self.start = 1
'''Not sure if these will be particularly useful, but they can be called if someone wants.'''
def PARTIAL(self):
return self.PARTIAL
def FULL(self):
return self.FULL
def FAIL(self):
return self.FAIL
# ~~~~~~~~~~~~~~~~~~~~
def execute(self, data):
'''Main functionality of the parser, checks if there is a complete http message; parses through the message if it's complete and returns from the function making all the data as the remainder content.'''
data = self.content + data
if not data:
self.error = 'There was no data in the parser\'s remainder and no data was passed in.'
return self.FAIL
self.content = ''
# Attempts to split the data based on the terminating characters of an http message.
# If there are more than 1 elements in the list there is a complete message.
message = data.split('\r\n\r\n',1)
if len(message) == 1:
self.content = self.content + data
return self.PARTIAL
# Goes through whatever wasn't a part of the first http request and puts it into the remainder content inserting '\r\n\r\n' where appropriate in case of multiple complete http requests
i = 1
while i < len(message):
self.content = self.content + message[i]
if not (i == (len(message) - 1)):
self.content = self.content + '\r\n\r\n'
i = i + 1
# Splits the http request into separate lines.
# Each line is then split by the first ':'; if there are two elements it is added to the headers, if there is one it is the first line of the request, and if there is anything else the message is bad
lines = message[0].split('\r\n')
for l in lines:
if l == '':
continue
broke = l.split(':',1)
if self.start:
first = l.split()
self.start = 0 # First line is being done, it should never get back in here
size = len(first)
# There is probably a better way of doing this, a more dynamic way that allows for a wider variety of formats
if size > 3:
self.error = 'Too many elements in first line of request.'
return self.FAIL
if size < 2:
self.error = 'Too few elements in first line, request needs a method and URL.'
return self.FAIL
self.method_type = first[0]
self.url = first[1]
if size == 3:
self.version = first[2]
elif len(broke) == 2:
self.headers[broke[0]] = broke[1]
# I feel like there must be a better way of doing thing
head = broke[0].lower()
if head == 'content-length':
self.needed_body = int(broke[1])
# ~~~~~~~~~~~~~
else:
self.error = 'Unrecognized error, the request may be empty.'
return self.FAIL
self.start = 1
return self.FULL
def execute_body(self, data):
'''Parses the data to construct the entity body based on the content-length, returning FULL, PARTIAL, or FAIL.'''
data = self.content + data
if self.needed_body > len(data):
self.body = self.body + data
self.needed_body = self.needed_body - len(data)
if self.needed_body:
return self.PARTIAL
else:
return self.FULL
self.body = self.body + data[:self.needed_body]
self.needed_body = 0
return self.FULL
def get_method_type(self):
'''Returns the method type (GET, POST, etc) or None if there is not a full message.'''
return self.method_type
def get_url(self):
'''Returns the urls of the request or None if there is not a full message.'''
return self.url
def get_version(self):
'''Returns the HTTP version of the request or None if there is not a full message.'''
return self.version
def get_headers(self):
'''Returns the headers of the message or None if there is not a full message.'''
return self.headers
def get_remainder(self):
'''Returns all the unused data that was received but not included in a message (for caching).'''
return self.content
def get_body(self):
'''Returns the entity body if there is one; returns None if there isn't one or if the message isn't full.'''
return self.body
def get_error(self):
'''Returns the error message if FAIL is ever returned.'''
return self.error
def get_needed_body(self):
'''Returns the value of needed_body, or the number of bytes needed to be added to the entity body.'''
return self.needed_body
def test(self,request):
'''A simple function for testing, not really useful in actually using the parser, but is more for developing the parser. Takes in a request message, calls execute, prints out relevant data members, tries to parse the entity body and prints out the body if there is one.'''
result = self.execute(request)
if result == self.FULL:
print 'FULL'
if result == self.PARTIAL:
print 'PARTIAL'
if result == self.FAIL:
print 'FAIL'
print 'Headers: ' + str(self.get_headers())
print 'Type: ' + str(self.get_method_type())
print 'Url: ' + str(self.get_url())
print 'Version: ' + str(self.get_version())
print 'Remainder: ' + str(self.get_remainder())
print 'Error: ' + str(self.get_error())
print 'Calling execute_body()'
f.execute_body(self.get_remainder())
print 'Entity body: ' + str(self.get_body())
print '\n'
version = '0.1'
if __name__ == '__main__':
full = 'GET http://google.com\r\nfirst: this is first\r\nsecond:this is second\r\n\r\n'
f = RealHTTP()
f.test(full)
# End of RealHTTP.py