#!/usr/bin/env python3 # File: fifconfig.py # Location: https://gitlab.com/bgstack15/fifconfig # Author: bgstack15 # SPDX-License-Identifier: GPL-3.0 # Startdate: 2022-03-15 20:35 # Title: Flask-ifconfig # Purpose: Act similar to http://ifconfig.me but with flask, for my network testing usage # History: # Usage: # Reference: # https://tedboy.github.io/flask/generated/generated/flask.Request.html # https://bgstack15.ddns.net/cgit/stackbin/tree/stackbin.py # https://pypi.org/project/dicttoxml/ # Improve: # Dependencies: # pip-devuan: json2html # pip-centos7: json2html # dep-devuan: python3-dicttoxml | python3-xmltodict # dep-centos7: python36-xmltodict # reverse-proxy configs that include X-Forwarded-Prefix headers. # Documentation: see README.md from flask import Flask, request, jsonify, url_for import os app = Flask(__name__) try: app.config.from_pyfile(os.environ['FIFCONFIG_CONF']) except: app.config.from_pyfile('fifconfig.conf') def _make_dict_safe_for_text(response): """ Mostly for converting the silly x-forwarded-for ImmutableList so that it does not show that class name. """ # This works except for the silly "ImmutableList([" text output for that one 'via' item. #response = { i:str(response[i]) for i in response } # So instead of that beautiful oneliner, you get this stupid 9-line block. gen = (i for i in response) new_response = {} for i in gen: if "" == str(type(response[i])): #print(f"i {i} is a immutablelist") new_response[i] = ','.join(response[i]) else: new_response[i] = str(response[i]) return new_response def get_attribs( request, lia = False, ia = False, ua = False, l = False, re = False, m = False, e = False, mt = False, c = False, xff = False ): """ Main function that builds the desired response dict that will later be turned into json or html by the various small @app.route functions. """ response = {} r = request rh = r.headers if lia: response['lastipaddress'] = r.remote_addr if ia: response['ipaddress'] = r.remote_addr if r.access_route and len(r.access_route) > 0: response['ipaddress'] = r.access_route[0] if ua: response['useragent'] = str(r.user_agent) if l: response['language'] = r.accept_languages if re: response['referer'] = r.referrer if m: response['method'] = r.method if e: response['encoding'] = r.accept_encodings if mt: response['mimetype'] = r.accept_mimetypes if c: response['charset'] = r.accept_charsets if xff: #response['x-forwarded-for'] = rh.get('X-Forwarded-For') or '' response['x-forwarded-for'] = r.access_route #'charset': rh.get('Accept-Charset') or '', #'endpoint': request.endpoint # via is the same as x-forwarded-for #'via': r.access_route print(f"DEBUG: dict is {response}") return response def prepare_output(request, response): """ Used to customize the output to json, html, or text depending on what the client asked for. In order of most important to least important: 1. request argument, i.e., '?json' 2. Accept-Mimetypes header """ possible_formats = ['json','html','text','xml'] _format = "text" # priority two for i in request.accept_mimetypes: for j in i: if 'application/xml' == j and _format != "html": _format = "xml" if 'application/json' == j: _format = "json" if 'application/xhtml+xml' == j or 'text/html' == j: _format = "html" if 'text/plain' == j: _format = "text" # priority one if request.args: if 'xml' in request.args: _format = "xml" if 'json' in request.args: _format = "json" if 'html' in request.args: _format = "html" if 'text' in request.args: _format = "text" if _format not in possible_formats: print(f"DEBUG (prepare_output): how did format {_format} get defined?! Using text.") _format = "text" # main process if "html" == _format: print("Sending html") if True: from json2html import json2html response = _make_dict_safe_for_text(response) response = json2html.convert(json = response) if not 'nolinks' in request.args: prefix = '' if 'HTTP_X_FORWARDED_PREFIX' in request.environ: prefixes = request.environ['HTTP_X_FORWARDED_PREFIX'] prefix = ''.join(prefixes.split(',')).replace(' ','') response += f"
" response += f"ip " response += f"ua " response += f"lang " response += f"encoding " response += f"mime " response += f"charset " response += f"forwarded " #response += f"

{request.environ}

" response += f"SOURCE" response += f"

" return response else: print("Unable to load json2html, sending plain text.") return str(response) elif "xml" == _format: print("Sending xml") pretty = False if request.args and 'pretty' in request.args: pretty = True try: import xmltodict response = xmltodict.unparse({'info':response}) except: try: # This lib is objectively better but not available natively on CentOS 7 print("Trying dicttoxml") from dicttoxml import dicttoxml response = dicttoxml(response,custom_root="info") except: print("Unable to load xmltodict, sending plain text.") return str(response) if pretty: try: from xml.dom.minidom import parseString response = parseString(response).toprettyxml() except: pass return response elif "json" == _format: print("Sending json") return jsonify(response) else: # the only other option is text print("Sending text") response = _make_dict_safe_for_text(response) new_response = "" if 1 == len(response): for i in response: return response[i] else: for i in response: new_response = new_response + '\n' + str(i) + ': ' + str(response[i]) return new_response @app.route('/') def root(): response = get_attribs(request, lia = False, ia = True, ua = True, l = True, re = True, m = True, e = True, mt = True, c = True, xff = True) response = prepare_output(request, response) return response @app.route('/ip') def ip(): return prepare_output(request, get_attribs(request, ia = True)) @app.route('/ua') def ua(): return prepare_output(request, get_attribs(request, ua = True)) @app.route('/lang') def lang(): return prepare_output(request, get_attribs(request, l = True)) @app.route('/encoding') def encoding(): return prepare_output(request, get_attribs(request, e = True)) @app.route('/mime') def mime(): return prepare_output(request, get_attribs(request, mt = True)) @app.route('/charset') def charset(): return prepare_output(request, get_attribs(request, c = True)) @app.route('/forwarded') def forwarded(): return prepare_output(request, get_attribs(request, xff = True)) if __name__ == "__main__": app.run()