diff --git a/content.py b/content.py index 5d0a54a6b..113c359fc 100644 --- a/content.py +++ b/content.py @@ -1479,6 +1479,11 @@ def extract_text_fields_in_post(post_bytes, boundary: str, debug: bool, """Returns a dictionary containing the text fields of a http form POST The boundary argument comes from the http header """ + if boundary == 'LYNX': + if debug: + print('POST from lynx browser') + boundary = '--LYNX' + if not unit_test_data: msg_bytes = email.parser.BytesParser().parsebytes(post_bytes) message_fields = msg_bytes.get_payload(decode=True).decode('utf-8') @@ -1486,7 +1491,8 @@ def extract_text_fields_in_post(post_bytes, boundary: str, debug: bool, message_fields = unit_test_data if debug: - print('DEBUG: POST arriving ' + message_fields) + if 'password' not in message_fields: + print('DEBUG: POST arriving ' + message_fields) message_fields = message_fields.split(boundary) fields = {} @@ -1495,6 +1501,10 @@ def extract_text_fields_in_post(post_bytes, boundary: str, debug: bool, 'instanceDescription', 'instanceDescriptionShort', 'subject', 'location', 'imageDescription' ) + if debug: + if 'password' not in message_fields: + print('DEBUG: POST message_fields: ' + str(message_fields)) + lynx_content_type = 'Content-Type: text/plain; charset=utf-8\r\n' # examine each section of the POST, separated by the boundary for fld in message_fields: if fld == '--': @@ -1505,14 +1515,30 @@ def extract_text_fields_in_post(post_bytes, boundary: str, debug: bool, if '"' not in post_str: continue post_key = post_str.split('"', 1)[0] + if debug: + print('post_key: ' + post_key) post_value_str = post_str.split('"', 1)[1] + if boundary == '--LYNX': + post_value_str = \ + post_value_str.replace(lynx_content_type, '') + if debug and 'password' not in post_key: + print('boundary: ' + boundary) + print('post_value_str1: ' + post_value_str) if ';' in post_value_str: if post_key not in fields_with_semicolon_allowed and \ not post_key.startswith('edited'): + if debug: + print('extract_text_fields_in_post exit 1') continue + if debug and 'password' not in post_key: + print('post_value_str2: ' + post_value_str) if '\r\n' not in post_value_str: + if debug: + print('extract_text_fields_in_post exit 2') continue post_lines = post_value_str.split('\r\n') + if debug and 'password' not in post_key: + print('post_lines: ' + str(post_lines)) post_value = '' if len(post_lines) > 2: for line in range(2, len(post_lines)-1): diff --git a/daemon.py b/daemon.py index dd82f995c..c1d21ed83 100644 --- a/daemon.py +++ b/daemon.py @@ -4739,25 +4739,28 @@ class PubServer(BaseHTTPRequestHandler): users_path = path.replace('/linksdata', '') users_path = users_path.replace('/editlinks', '') actor_str = self._get_instance_url(calling_domain) + users_path + + boundary = None if ' boundary=' in self.headers['Content-type']: boundary = self.headers['Content-type'].split('boundary=')[1] if ';' in boundary: boundary = boundary.split(';')[0] - # get the nickname - nickname = get_nickname_from_actor(actor_str) - editor = None - if nickname: - editor = is_editor(base_dir, nickname) - if not nickname or not editor: - if not nickname: - print('WARN: nickname not found in ' + actor_str) - else: - print('WARN: nickname is not a moderator' + actor_str) - self._redirect_headers(actor_str, cookie, calling_domain) - self.server.postreq_busy = False - return + # get the nickname + nickname = get_nickname_from_actor(actor_str) + editor = None + if nickname: + editor = is_editor(base_dir, nickname) + if not nickname or not editor: + if not nickname: + print('WARN: nickname not found in ' + actor_str) + else: + print('WARN: nickname is not a moderator' + actor_str) + self._redirect_headers(actor_str, cookie, calling_domain) + self.server.postreq_busy = False + return + if self.headers.get('Content-length'): length = int(self.headers['Content-length']) # check that the POST isn't too large @@ -4767,32 +4770,37 @@ class PubServer(BaseHTTPRequestHandler): self.server.postreq_busy = False return - try: - # read the bytes of the http form POST - post_bytes = self.rfile.read(length) - except SocketError as ex: - if ex.errno == errno.ECONNRESET: - print('EX: connection was reset while ' + - 'reading bytes from http form POST') - else: - print('EX: error while reading bytes ' + - 'from http form POST') - self.send_response(400) - self.end_headers() - self.server.postreq_busy = False - return - except ValueError as ex: - print('EX: failed to read bytes for POST, ' + str(ex)) - self.send_response(400) - self.end_headers() - self.server.postreq_busy = False - return + try: + # read the bytes of the http form POST + post_bytes = self.rfile.read(length) + except SocketError as ex: + if ex.errno == errno.ECONNRESET: + print('EX: connection was reset while ' + + 'reading bytes from http form POST') + else: + print('EX: error while reading bytes ' + + 'from http form POST') + self.send_response(400) + self.end_headers() + self.server.postreq_busy = False + return + except ValueError as ex: + print('EX: failed to read bytes for POST, ' + str(ex)) + self.send_response(400) + self.end_headers() + self.server.postreq_busy = False + return - links_filename = base_dir + '/accounts/links.txt' - about_filename = base_dir + '/accounts/about.md' - tos_filename = base_dir + '/accounts/tos.md' - specification_filename = base_dir + '/accounts/activitypub.md' + links_filename = base_dir + '/accounts/links.txt' + about_filename = base_dir + '/accounts/about.md' + tos_filename = base_dir + '/accounts/tos.md' + specification_filename = base_dir + '/accounts/activitypub.md' + if not boundary: + if b'--LYNX' in post_bytes: + boundary = '--LYNX' + + if boundary: # extract all of the text fields into a dict fields = \ extract_text_fields_in_post(post_bytes, boundary, debug) @@ -4918,25 +4926,28 @@ class PubServer(BaseHTTPRequestHandler): users_path = users_path.split('/tags/')[0] actor_str = self._get_instance_url(calling_domain) + users_path tag_screen_str = actor_str + '/tags/' + hashtag + + boundary = None if ' boundary=' in self.headers['Content-type']: boundary = self.headers['Content-type'].split('boundary=')[1] if ';' in boundary: boundary = boundary.split(';')[0] - # get the nickname - nickname = get_nickname_from_actor(actor_str) - editor = None - if nickname: - editor = is_editor(base_dir, nickname) - if not hashtag or not editor: - if not nickname: - print('WARN: nickname not found in ' + actor_str) - else: - print('WARN: nickname is not a moderator' + actor_str) - self._redirect_headers(tag_screen_str, cookie, calling_domain) - self.server.postreq_busy = False - return + # get the nickname + nickname = get_nickname_from_actor(actor_str) + editor = None + if nickname: + editor = is_editor(base_dir, nickname) + if not hashtag or not editor: + if not nickname: + print('WARN: nickname not found in ' + actor_str) + else: + print('WARN: nickname is not a moderator' + actor_str) + self._redirect_headers(tag_screen_str, cookie, calling_domain) + self.server.postreq_busy = False + return + if self.headers.get('Content-length'): length = int(self.headers['Content-length']) # check that the POST isn't too large @@ -4946,27 +4957,32 @@ class PubServer(BaseHTTPRequestHandler): self.server.postreq_busy = False return - try: - # read the bytes of the http form POST - post_bytes = self.rfile.read(length) - except SocketError as ex: - if ex.errno == errno.ECONNRESET: - print('EX: connection was reset while ' + - 'reading bytes from http form POST') - else: - print('EX: error while reading bytes ' + - 'from http form POST') - self.send_response(400) - self.end_headers() - self.server.postreq_busy = False - return - except ValueError as ex: - print('EX: failed to read bytes for POST, ' + str(ex)) - self.send_response(400) - self.end_headers() - self.server.postreq_busy = False - return + try: + # read the bytes of the http form POST + post_bytes = self.rfile.read(length) + except SocketError as ex: + if ex.errno == errno.ECONNRESET: + print('EX: connection was reset while ' + + 'reading bytes from http form POST') + else: + print('EX: error while reading bytes ' + + 'from http form POST') + self.send_response(400) + self.end_headers() + self.server.postreq_busy = False + return + except ValueError as ex: + print('EX: failed to read bytes for POST, ' + str(ex)) + self.send_response(400) + self.end_headers() + self.server.postreq_busy = False + return + if not boundary: + if b'--LYNX' in post_bytes: + boundary = '--LYNX' + + if boundary: # extract all of the text fields into a dict fields = \ extract_text_fields_in_post(post_bytes, boundary, debug) @@ -5000,25 +5016,28 @@ class PubServer(BaseHTTPRequestHandler): users_path = path.replace('/newswiredata', '') users_path = users_path.replace('/editnewswire', '') actor_str = self._get_instance_url(calling_domain) + users_path + + boundary = None if ' boundary=' in self.headers['Content-type']: boundary = self.headers['Content-type'].split('boundary=')[1] if ';' in boundary: boundary = boundary.split(';')[0] - # get the nickname - nickname = get_nickname_from_actor(actor_str) - moderator = None - if nickname: - moderator = is_moderator(base_dir, nickname) - if not nickname or not moderator: - if not nickname: - print('WARN: nickname not found in ' + actor_str) - else: - print('WARN: nickname is not a moderator' + actor_str) - self._redirect_headers(actor_str, cookie, calling_domain) - self.server.postreq_busy = False - return + # get the nickname + nickname = get_nickname_from_actor(actor_str) + moderator = None + if nickname: + moderator = is_moderator(base_dir, nickname) + if not nickname or not moderator: + if not nickname: + print('WARN: nickname not found in ' + actor_str) + else: + print('WARN: nickname is not a moderator' + actor_str) + self._redirect_headers(actor_str, cookie, calling_domain) + self.server.postreq_busy = False + return + if self.headers.get('Content-length'): length = int(self.headers['Content-length']) # check that the POST isn't too large @@ -5028,29 +5047,34 @@ class PubServer(BaseHTTPRequestHandler): self.server.postreq_busy = False return - try: - # read the bytes of the http form POST - post_bytes = self.rfile.read(length) - except SocketError as ex: - if ex.errno == errno.ECONNRESET: - print('EX: connection was reset while ' + - 'reading bytes from http form POST') - else: - print('EX: error while reading bytes ' + - 'from http form POST') - self.send_response(400) - self.end_headers() - self.server.postreq_busy = False - return - except ValueError as ex: - print('EX: failed to read bytes for POST, ' + str(ex)) - self.send_response(400) - self.end_headers() - self.server.postreq_busy = False - return + try: + # read the bytes of the http form POST + post_bytes = self.rfile.read(length) + except SocketError as ex: + if ex.errno == errno.ECONNRESET: + print('EX: connection was reset while ' + + 'reading bytes from http form POST') + else: + print('EX: error while reading bytes ' + + 'from http form POST') + self.send_response(400) + self.end_headers() + self.server.postreq_busy = False + return + except ValueError as ex: + print('EX: failed to read bytes for POST, ' + str(ex)) + self.send_response(400) + self.end_headers() + self.server.postreq_busy = False + return - newswire_filename = base_dir + '/accounts/newswire.txt' + newswire_filename = base_dir + '/accounts/newswire.txt' + if not boundary: + if b'--LYNX' in post_bytes: + boundary = '--LYNX' + + if boundary: # extract all of the text fields into a dict fields = \ extract_text_fields_in_post(post_bytes, boundary, debug) @@ -5269,30 +5293,33 @@ class PubServer(BaseHTTPRequestHandler): users_path = path.replace('/newseditdata', '') users_path = users_path.replace('/editnewspost', '') actor_str = self._get_instance_url(calling_domain) + users_path + + boundary = None if ' boundary=' in self.headers['Content-type']: boundary = self.headers['Content-type'].split('boundary=')[1] if ';' in boundary: boundary = boundary.split(';')[0] - # get the nickname - nickname = get_nickname_from_actor(actor_str) - editor_role = None - if nickname: - editor_role = is_editor(base_dir, nickname) - if not nickname or not editor_role: - if not nickname: - print('WARN: nickname not found in ' + actor_str) - else: - print('WARN: nickname is not an editor' + actor_str) - if self.server.news_instance: - self._redirect_headers(actor_str + '/tlfeatures', - cookie, calling_domain) - else: - self._redirect_headers(actor_str + '/tlnews', - cookie, calling_domain) - self.server.postreq_busy = False - return + # get the nickname + nickname = get_nickname_from_actor(actor_str) + editor_role = None + if nickname: + editor_role = is_editor(base_dir, nickname) + if not nickname or not editor_role: + if not nickname: + print('WARN: nickname not found in ' + actor_str) + else: + print('WARN: nickname is not an editor' + actor_str) + if self.server.news_instance: + self._redirect_headers(actor_str + '/tlfeatures', + cookie, calling_domain) + else: + self._redirect_headers(actor_str + '/tlnews', + cookie, calling_domain) + self.server.postreq_busy = False + return + if self.headers.get('Content-length'): length = int(self.headers['Content-length']) # check that the POST isn't too large @@ -5307,27 +5334,32 @@ class PubServer(BaseHTTPRequestHandler): self.server.postreq_busy = False return - try: - # read the bytes of the http form POST - post_bytes = self.rfile.read(length) - except SocketError as ex: - if ex.errno == errno.ECONNRESET: - print('EX: connection was reset while ' + - 'reading bytes from http form POST') - else: - print('EX: error while reading bytes ' + - 'from http form POST') - self.send_response(400) - self.end_headers() - self.server.postreq_busy = False - return - except ValueError as ex: - print('EX: failed to read bytes for POST, ' + str(ex)) - self.send_response(400) - self.end_headers() - self.server.postreq_busy = False - return + try: + # read the bytes of the http form POST + post_bytes = self.rfile.read(length) + except SocketError as ex: + if ex.errno == errno.ECONNRESET: + print('EX: connection was reset while ' + + 'reading bytes from http form POST') + else: + print('EX: error while reading bytes ' + + 'from http form POST') + self.send_response(400) + self.end_headers() + self.server.postreq_busy = False + return + except ValueError as ex: + print('EX: failed to read bytes for POST, ' + str(ex)) + self.send_response(400) + self.end_headers() + self.server.postreq_busy = False + return + if not boundary: + if b'--LYNX' in post_bytes: + boundary = '--LYNX' + + if boundary: # extract all of the text fields into a dict fields = \ extract_text_fields_in_post(post_bytes, boundary, debug) @@ -5409,19 +5441,22 @@ class PubServer(BaseHTTPRequestHandler): users_path = path.replace('/profiledata', '') users_path = users_path.replace('/editprofile', '') actor_str = self._get_instance_url(calling_domain) + users_path + + boundary = None if ' boundary=' in self.headers['Content-type']: boundary = self.headers['Content-type'].split('boundary=')[1] if ';' in boundary: boundary = boundary.split(';')[0] - # get the nickname - nickname = get_nickname_from_actor(actor_str) - if not nickname: - print('WARN: nickname not found in ' + actor_str) - self._redirect_headers(actor_str, cookie, calling_domain) - self.server.postreq_busy = False - return + # get the nickname + nickname = get_nickname_from_actor(actor_str) + if not nickname: + print('WARN: nickname not found in ' + actor_str) + self._redirect_headers(actor_str, cookie, calling_domain) + self.server.postreq_busy = False + return + if self.headers.get('Content-length'): length = int(self.headers['Content-length']) # check that the POST isn't too large @@ -5432,29 +5467,34 @@ class PubServer(BaseHTTPRequestHandler): self.server.postreq_busy = False return - try: - # read the bytes of the http form POST - post_bytes = self.rfile.read(length) - except SocketError as ex: - if ex.errno == errno.ECONNRESET: - print('EX: connection was reset while ' + - 'reading bytes from http form POST') - else: - print('EX: error while reading bytes ' + - 'from http form POST') - self.send_response(400) - self.end_headers() - self.server.postreq_busy = False - return - except ValueError as ex: - print('EX: failed to read bytes for POST, ' + str(ex)) - self.send_response(400) - self.end_headers() - self.server.postreq_busy = False - return + try: + # read the bytes of the http form POST + post_bytes = self.rfile.read(length) + except SocketError as ex: + if ex.errno == errno.ECONNRESET: + print('EX: connection was reset while ' + + 'reading bytes from http form POST') + else: + print('EX: error while reading bytes ' + + 'from http form POST') + self.send_response(400) + self.end_headers() + self.server.postreq_busy = False + return + except ValueError as ex: + print('EX: failed to read bytes for POST, ' + str(ex)) + self.send_response(400) + self.end_headers() + self.server.postreq_busy = False + return - admin_nickname = get_config_param(self.server.base_dir, 'admin') + admin_nickname = get_config_param(self.server.base_dir, 'admin') + if not boundary: + if b'--LYNX' in post_bytes: + boundary = '--LYNX' + + if boundary: # get the various avatar, banner and background images actor_changed = True profile_media_types = ( diff --git a/tests.py b/tests.py index 6bbe9e32d..b56e03bc7 100644 --- a/tests.py +++ b/tests.py @@ -5818,6 +5818,20 @@ def _test_markdown_to_html(): def _test_extract_text_fields_from_post(): print('test_extract_text_fields_in_post') + boundary = '--LYNX' + form_data = '--LYNX\r\nContent-Disposition: form-data; ' + \ + 'name="fieldName"\r\nContent-Type: text/plain; ' + \ + 'charset=utf-8\r\n\r\nThis is a lynx test\r\n' + \ + '--LYNX\r\nContent-Disposition: ' + \ + 'form-data; name="submitYes"\r\nContent-Type: text/plain; ' + \ + 'charset=utf-8\r\n\r\nBUTTON\r\n--LYNX--\r\n' + debug = True + fields = extract_text_fields_in_post(None, boundary, debug, form_data) + print('fields: ' + str(fields)) + assert fields + assert fields['fieldName'] == 'This is a lynx test' + assert fields['submitYes'] == 'BUTTON' + boundary = '-----------------------------116202748023898664511855843036' form_data = '-----------------------------116202748023898664511855' + \ '843036\r\nContent-Disposition: form-data; name="submitPost"' + \