Преглед изворни кода

Merge pull request #986 from 2du/dev

Dev
잉여개발기 (SPDV) пре 5 година
родитељ
комит
0aaa6f5e3d

+ 13 - 9
app.py

@@ -514,6 +514,10 @@ def topic_close_list(name = 'test'):
 def user_tool(name = None):
     return user_tool_2(conn, name)
 
+@app.route('/2fa_login', methods=['POST', 'GET'])
+def login_2fa():
+    return login_2fa_2(conn)
+
 @app.route('/login', methods=['POST', 'GET'])
 def login():
     return login_2(conn)
@@ -618,7 +622,7 @@ def user_count_edit(name = None):
 def func_title_random():
     return func_title_random_2(conn)
 
-@app.route('/image/<name>')
+@app.route('/image/<everything:name>')
 def main_image_view(name = None):
     return main_image_view_2(conn, name, app_var)
 
@@ -701,7 +705,7 @@ def api_sha224(name = 'test'):
 def api_title_index():
     return api_title_index_2(conn)
 
-@app.route('/api/image/<name>')
+@app.route('/api/image/<everything:name>')
 def api_image_view(name = ''):
     return api_image_view_2(conn, name, app_var)
 
@@ -727,11 +731,11 @@ app.secret_key = rep_key
 app.wsgi_app = werkzeug.debug.DebuggedApplication(app.wsgi_app, True)
 app.debug = True
 
-if __name__ == "__main__":
-    if sys.platform == 'win32' and sys.version_info[0:2] >= (3, 8):
-        asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
-
-    http_server = tornado.httpserver.HTTPServer(tornado.wsgi.WSGIContainer(app))
-    http_server.listen(int(server_set['port']), address = server_set['host'])
+# https://stackoverflow.com/questions/31433682/control-wsgiref-simple-server-log
+class NoLoggingWSGIRequestHandler(wsgiref.simple_server.WSGIRequestHandler):
+    def log_message(self, format, *args):
+        pass
 
-    tornado.ioloop.IOLoop.instance().start()
+httpd = wsgiref.simple_server.make_server(server_set['host'], int(server_set['port']), app, handler_class = NoLoggingWSGIRequestHandler)
+if __name__ == "__main__":
+    httpd.serve_forever()

+ 10 - 1
emergency_tool.py

@@ -134,6 +134,7 @@ print('12. All title count reset')
 print('13. Cache data reset')
 print('14. Delete Main <HEAD>')
 print('15. Give owner')
+print('16. Delete 2FA password')
 
 print('----')
 print('Select : ', end = '')
@@ -284,12 +285,20 @@ elif what_i_do == '13':
     curs.execute(db_change('delete from cache_data'))
 elif what_i_do == '14':
     curs.execute(db_change('delete from other where name = "head"'))
-else:
+elif what_i_do == '15':
     print('----')
     print('User name : ', end = '')
     user_name = input()
 
     curs.execute(db_change("update user set acl = 'owner' where id = ?"), [user_name])
+else:
+    print('----')
+    print('User name : ', end = '')
+    user_name = input()
+
+    curs.execute(db_change('select data from user_set where name = "2fa" and id = ?'), [user_name])
+    if curs.fetchall():
+        curs.execute(db_change("update user_set set data = '' where name = '2fa' and id = ?"), [user_name])
 
 conn.commit()
 

+ 4 - 0
language/en-US.json

@@ -96,6 +96,7 @@
         "icon" : "Icon",
         "view" : "View",
         "content" : "Content",
+        "on" : "On",
         "off" : "Off",
         "unset" : "Unset",
         "extension" : "Extension",
@@ -210,6 +211,9 @@
         "get_sitemap" : "Create or renewal sitemap.xml",
         "simple_check" : "Simple check",
         "add_user" : "Add user",
+        "2fa" : "2FA",
+        "2fa_password" : "2FA password",
+        "2fa_password_change" : "Change 2FA password",
         "_comment_2.1_" : "Filter",
             "_comment_2.1.1_" : "List",
                 "interwiki_list" : "Interwiki(s) list",

+ 5 - 1
language/ko-KR.json

@@ -388,5 +388,9 @@
     "1_line_1_q": "1줄당 1개의 선택지를 쓰세요.",
     "open_vote_list" : "열린 투표 목록",
     "close_vote" : "투표 닫기",
-    "re_open_vote" : "투표 다시 열기"
+    "re_open_vote" : "투표 다시 열기",
+    "on" : "켜기",
+    "2fa" : "2차 인증",
+    "2fa_password" : "2차 비밀번호",
+    "2fa_password_change" : "2차 비밀번호 변경"
 }

+ 13 - 43
readme-en.md

@@ -1,5 +1,5 @@
-openNAMU
-====
+[(en-US)](./readme-en.md) | [(ko-KR)](./readme.md)
+# openNAMU
 [![Up to Python 3.5](https://img.shields.io/badge/python->=%203.5-blue.svg)](https://python.org)
 [![LICENSE](https://img.shields.io/badge/license-BSD%203--Clause-lightgrey.svg)](./LICENSE)
 
@@ -12,71 +12,41 @@ openNAMU is a Python-based wiki engine. You can use openNAMU by installing Pytho
  * [Clone](#clone)
  * [Contribute](#contribute)
  * [License](#license)
- * [Authors](#authors)
  * [Etc.](#etc)
 
-# Getting Started
+## Getting Started
 openNAMU is based upon Python, and it requires a Python environment.
 
-### Download Releases
-Download the [release version of openNAMU](https://github.com/2du/openNAMU/releases), and unzip the file. It is also possible to download releases by [cloning this repository](#Clone).
+[Here](http://2du.pythonanywhere.com) you can see the guide.
 
-### Install Modules
-Windows
-```
-pip install -r requirements.txt
-```
-
-Linux
-```
-pip3 install -r requirements.txt
-```
-## Launching Application
-Windows
-```
-python app.py
-```
-
-Linux
-```
-python3 app.py
-```
-
-## Publishing Application
-
-# Clone
+## Clone
 You can clone this repository by entering the following command at the terminal (command prompt):
-## Stable
  * `git clone -b stable https://github.com/2du/openNAMU.git`
-
-## Beta
  * `git clone -b master https://github.com/2du/openNAMU.git`
 
-# Contribute
-openNAMU may have some untested bugs. Your use of openNAMU and bug discovery will help develop openNAMU.
-[[Create Issues]](https://github.com/2du/openNAMU/issues/new)
+## Contribute
+openNAMU may have some untested bugs. Your use of openNAMU and bug discovery will help develop openNAMU. [(Create Issues)](https://github.com/2du/openNAMU/issues/new)
 
-openNAMU is a complete open source project. Add new features and create pull requests.
-[[Create Pull Requests]](https://github.com/2du/openNAMU/compare)
+openNAMU is a complete open source project. Add new features and create pull requests. [(Create Pull Requests)](https://github.com/2du/openNAMU/compare)
 
-# Lisence
+## Lisence
 openNAMU is protected by [BSD 3-Clause License](./LICNESE). Please refer to the documentation for details.
 
-## External Projects
+### External Projects
  * Quotes icon [Dave Gandy](http://www.flaticon.com/free-icon/quote-left_25672)
  * Syntax highlighting [highlightjs](https://highlightjs.org/)
  * Numerical expression [MathJax](https://www.mathjax.org/)
  * Handling Keyboard Shortcuts [shortcut.js](http://www.openjs.com/scripts/events/keyboard_shortcuts/)
 
-# Authors
+### Authors
  * [Reference](https://github.com/2DU/openNAMU/graphs/contributors)
 
-## Special Thanks
+### Special Thanks
  * [Team Croatia](https://github.com/TeamCroatia)
  * Basix
  * Efrit
  * Other chat rooms
 
-# Etc.
+## Etc.
  * Owner rights are granted to the first registor.
  * [Test Server](http://2du.pythonanywhere.com)

+ 9 - 12
readme.md

@@ -1,5 +1,5 @@
-오픈나무
-====
+[(en-US)](./readme-en.md) | [(ko-KR)](./readme.md)
+# 오픈나무
 [![Python 3.5 이상](https://img.shields.io/badge/python->=%203.5-blue.svg)](https://python.org)
 [![라이선스](https://img.shields.io/badge/license-BSD%203--Clause-lightgrey.svg)](./LICENSE)
 
@@ -7,12 +7,12 @@
 
 오픈나무는 파이썬 기반의 위키 엔진입니다. 파이썬과 그 의존성 모듈만 설치하면 사용할 수 있으며, 코드를 직접 수정하여 좀 더 주제에 특화된 위키를 만들 수 있습니다.
 
- * [(README for english)](./readme-en.md)
 ### 목차
+ * [시작하기](#시작하기)
  * [클론](#클론)
  * [기여](#기여)
  * [라이선스](#라이선스)
- * [기여자 목록](#기여자-목록)
+ * [지원 문법](#지원-문법)
  * [기타](#기타)
 
 ## 시작하기
@@ -22,10 +22,7 @@
 
 ## 클론
 아래 명령을 터미널(명령 프롬프트)에 입력하여 본 리포지토리를 클론할 수 있습니다.
-### 일반
  * `git clone -b stable https://github.com/2du/openNAMU.git`
-
-### 개발판
  * `git clone -b master https://github.com/2du/openNAMU.git`
 
 ## 기여
@@ -42,11 +39,7 @@
  * Numerical expression - [MathJax](https://www.mathjax.org/)
  * Handling Keyboard Shortcuts [shortcut.js](http://www.openjs.com/scripts/events/keyboard_shortcuts/)
 
-## 지원 문법
- * 나무마크 (NamuMark)
- * 마크다운 (Markdown) (예정)
-
-## 기여자 목록
+### 기여자 목록
  * [참고](https://github.com/2DU/openNAMU/graphs/contributors)
 
 ### 도움을 주신 분들
@@ -55,6 +48,10 @@
  * Efrit
  * 기타 채팅방 사람들
 
+## 지원 문법
+ * 나무마크 (NamuMark)
+ * 마크다운 (Markdown) (예정)
+
 ## 기타
  * 첫 가입자에게 소유자 권한이 부여됩니다.
  * [테스트 서버](http://2du.pythonanywhere.com)

+ 2 - 1
route/give_history_add.py

@@ -20,7 +20,8 @@ def give_history_add_2(conn, name):
             today,
             'Add:' + flask.request.form.get('get_ip', ''),
             flask.request.form.get('send', ''),
-            leng
+            leng,
+            mode = 'add'
         )
 
         conn.commit()

+ 27 - 14
route/login.py

@@ -17,8 +17,9 @@ def login_2(conn):
             captcha_post('', 0)
 
         agent = flask.request.headers.get('User-Agent')
+        user_id = flask.request.form.get('id', '')
 
-        curs.execute(db_change("select pw, encode from user where id = ?"), [flask.request.form.get('id', None)])
+        curs.execute(db_change("select pw, encode from user where id = ?"), [user_id])
         user = curs.fetchall()
         if not user:
             return re_error('/error/2')
@@ -27,28 +28,40 @@ def login_2(conn):
             flask.request.form.get('pw', ''),
             user[0][0],
             user[0][1],
-            flask.request.form.get('id', None)
+            user_id
         )
         if pw_check_d != 1:
             return re_error('/error/10')
 
-        flask.session['state'] = 1
-        flask.session['id'] = flask.request.form.get('id', None)
+        curs.execute(db_change('select data from user_set where name = "2fa" and id = ?'), [user_id])
+        fa_data = curs.fetchall()
+        if fa_data and fa_data[0][0] != '':
+            curs.execute(db_change("select css from custom where user = ?"), [user_id])
+            css_data = curs.fetchall()
+            flask.session['b_head'] = css_data[0][0] if css_data else ''
+            flask.session['b_id'] = user_id
 
-        curs.execute(db_change("select css from custom where user = ?"), [flask.request.form.get('id', None)])
-        css_data = curs.fetchall()
-        if css_data:
-            flask.session['head'] = css_data[0][0]
+            return redirect('/2fa_login')
         else:
-            flask.session['head'] = ''
+            curs.execute(db_change("select css from custom where user = ?"), [user_id])
+            css_data = curs.fetchall()
+            flask.session['head'] = css_data[0][0] if css_data else ''
+            flask.session['id'] = user_id
 
-        curs.execute(db_change("insert into ua_d (name, ip, ua, today, sub) values (?, ?, ?, ?, '')"), [flask.request.form.get('id', None), ip_check(1), agent, get_time()])
+            curs.execute(db_change("insert into ua_d (name, ip, ua, today, sub) values (?, ?, ?, ?, '')"), [
+                user_id, 
+                ip, 
+                agent, 
+                get_time()
+            ])
+            conn.commit()
 
-        conn.commit()
-
-        return redirect('/user')
+            return redirect('/user')
     else:
-        http_warring = '<hr class=\"main_hr\"><span>' + load_lang('http_warring') + '</span>'
+        http_warring = '' + \
+            '<hr class="main_hr">' + \
+            '<span>' + load_lang('http_warring') + '</span>' + \
+        ''
 
         return easy_minify(flask.render_template(skin_check(),
             imp = [load_lang('login'), wiki_set(), custom(), other2([0, 0])],

+ 74 - 0
route/login_2fa.py

@@ -0,0 +1,74 @@
+from .tool.func import *
+
+def login_2fa_2(conn):
+    curs = conn.cursor()
+
+    if not (flask.session and 'b_id' in flask.session):
+        return redirect('/user')
+
+    ip = ip_check()
+    if ip_or_user(ip) == 0:
+        return redirect('/user')
+
+    if ban_check(tool = 'login') == 1:
+        return re_error('/ban')
+
+    if flask.request.method == 'POST':
+        if captcha_post(flask.request.form.get('g-recaptcha-response', flask.request.form.get('g-recaptcha', ''))) == 1:
+            return re_error('/error/13')
+        else:
+            captcha_post('', 0)
+
+        agent = flask.request.headers.get('User-Agent')
+        user_id = flask.session['b_id']
+
+        curs.execute(db_change('select data from user_set where name = "2fa_pw" and id = ?'), [user_id])
+        user_1 = curs.fetchall()
+        if user_1:
+            curs.execute(db_change('select data from user_set where name = "2fa_pw_encode" and id = ?'), [user_id])
+            user_1 = user_1[0][0]
+            user_2 = curs.fetchall()[0][0]
+
+            pw_check_d = pw_check(
+                flask.request.form.get('pw', ''),
+                user_1,
+                user_2,
+                user_id
+            )
+            if pw_check_d != 1:
+                return re_error('/error/10')
+
+        flask.session['head'] = flask.session['b_head']
+        flask.session['id'] = user_id
+
+        curs.execute(db_change("insert into ua_d (name, ip, ua, today, sub) values (?, ?, ?, ?, '')"), [
+            user_id, 
+            ip, 
+            agent, 
+            get_time()
+        ])
+        conn.commit()
+
+        flask.session.pop('b_id', None)
+        flask.session.pop('b_head', None)
+
+        return redirect('/user')
+    else:
+        http_warring = '' + \
+            '<hr class="main_hr">' + \
+            '<span>' + load_lang('http_warring') + '</span>' + \
+        ''
+
+        return easy_minify(flask.render_template(skin_check(),
+            imp = [load_lang('login'), wiki_set(), custom(), other2([0, 0])],
+            data =  '''
+                    <form method="post">
+                        <input placeholder="''' + load_lang('2fa_password') + '''" name="pw" type="password">
+                        <hr class=\"main_hr\">
+                        ''' + captcha_get() + '''
+                        <button type="submit">''' + load_lang('login') + '''</button>
+                        ''' + http_warring + '''
+                    </form>
+                    ''',
+            menu = [['user', load_lang('return')]]
+        ))

+ 6 - 2
route/login_check_key.py

@@ -3,6 +3,7 @@ from .tool.func import *
 def login_check_key_2(conn, tool):
     curs = conn.cursor()
 
+    # 난잡한 코드 정리 필요
     if flask.request.method == 'POST':
         if tool == 'check_pass_key':
             if 'c_id' in flask.session and flask.session['c_key'] == flask.request.form.get('key', None):
@@ -20,10 +21,14 @@ def login_check_key_2(conn, tool):
                 curs.execute(db_change('select data from other where name = "reset_user_text"'))
                 sql_d = curs.fetchall()
                 if sql_d and sql_d[0][0] != '':
-                    b_text = sql_d[0][0] + '<hr class=\"main_hr\">'
+                    b_text = sql_d[0][0] + '<hr class="main_hr">'
                 else:
                     b_text = ''
 
+                curs.execute(db_change('select data from user_set where name = "2fa" and id = ?'), [d_id])
+                if curs.fetchall():
+                    curs.execute(db_change("update user_set set data = '' where name = '2fa' and id = ?"), [d_id])
+
                 return easy_minify(flask.render_template(skin_check(),
                     imp = [load_lang('reset_user_ok'), wiki_set(), custom(), other2([0, 0])],
                     data = b_text + load_lang('id') + ' : ' + d_id + '<br>' + load_lang('password') + ' : ' + pw,
@@ -101,7 +106,6 @@ def login_check_key_2(conn, tool):
                         get_time()
                     ])
 
-                    flask.session['state'] = 1
                     flask.session['id'] = flask.session['c_id']
                     flask.session['head'] = ''
 

+ 0 - 1
route/login_register.py

@@ -116,7 +116,6 @@ def login_register_2(conn):
 
             curs.execute(db_change("insert into ua_d (name, ip, ua, today, sub) values (?, ?, ?, ?, '')"), [flask.request.form.get('id', None), ip, agent, get_time()])
 
-            flask.session['state'] = 1
             flask.session['id'] = flask.request.form.get('id', None)
             flask.session['head'] = ''
 

+ 4 - 1
route/recent_changes.py

@@ -163,7 +163,10 @@ def recent_changes_2(conn, name, tool):
                 title = '<a href="/w/' + url_pas(name) + '?num=' + data[0] + '">r' + data[0] + '</a> '
             else:
                 title = '<a href="/w/' + url_pas(data[1]) + '">' + html.escape(data[1]) + '</a> '
-                title += '<a href="/history/' + url_pas(data[1]) + '">(r' + data[0] + ')</a> '
+                if int(data[0]) < 2:
+                    title += '<a href="/history/' + url_pas(data[1]) + '">(r' + data[0] + ')</a> '
+                else:
+                    title += '<a href="/diff/' + url_pas(data[1]) + '?first=' + str(int(data[0]) - 1) + '&second=' + data[0] + '">(r' + data[0] + ')</a> '
 
             div += '''
                 <tr ''' + style[0] + '''>

+ 20 - 42
route/recent_history_tool.py

@@ -5,56 +5,34 @@ def recent_history_tool_2(conn, name):
 
     num = str(int(number_check(flask.request.args.get('num', '1'))))
 
-    data = '''
-        <h2>''' + load_lang('tool') + '''</h2>
-        <ul>
-            <li>
-                <a href="/raw/''' + url_pas(name) + '?num=' + num + '">' + load_lang('raw') + '''</a>
-            </li>
-    '''
-
-    if (int(num) - 1) > 0:
-        data += '''
-            <li>
-                <a href="/diff/''' + url_pas(name) + '?first=''' + str(int(num) - 1) + '&second=' + num + '">' + load_lang('compare') + '''</a>
-            </li>
-        '''
+    data = '' + \
+        '<h2>' + load_lang('tool') + '</h2>' + \
+        '<ul>' + \
+            '<li><a href="/raw/' + url_pas(name) + '?num=' + num + '">' + load_lang('raw') + '</a></li>' + \
+    ''
 
     if flask.request.args.get('type', '') == 'history':
-        data += '''
-            <li>
-                <a href="/revert/''' + url_pas(name) + '?num=' + num + '">' + load_lang('revert') + '''</a>
-            </li>
-        '''
+        data += '<li><a href="/revert/' + url_pas(name) + '?num=' + num + '">' + load_lang('revert') + '</a></li>'
+        if (int(num) - 1) > 0:
+            data += '<li><a href="/diff/' + url_pas(name) + '?first=' + str(int(num) - 1) + '&second=' + num + '">' + load_lang('compare') + '</a></li>'
     elif (int(num) - 1) > 0:
-        data += '''
-            <li>
-                <a href="/revert/''' + url_pas(name) + '?num=' + str(int(num) - 1) + '">' + load_lang('revert') + '''</a>
-            </li>
-        '''
+        data += '<li><a href="/revert/' + url_pas(name) + '?num=' + str(int(num) - 1) + '">' + load_lang('revert') + '</a></li>'
+
+    if flask.request.args.get('type', '') != 'history':
+        data += '<li><a href="/history/' + url_pas(name) + '">' + load_lang('history') + '</a></li>'
 
     if admin_check(6) == 1:
-        curs.execute(db_change('''
-            select title from history
-            where title = ? and id = ? and hide = 'O'
-        '''), [name, num])
+        curs.execute(db_change('' + \
+            'select title from history ' + \
+            'where title = ? and id = ? and hide = "O"' + \
+        ''), [name, num])
         hide = curs.fetchall()
-        data += '''
-            <li>
-                <a href="/hidden/''' + url_pas(name) + '?num=' + num + '">' + (load_lang('hide_release') if hide else load_lang('hide')) + '''
-            </li>
-        '''
+        data += '<li><a href="/hidden/' + url_pas(name) + '?num=' + num + '">' + (load_lang('hide_release') if hide else load_lang('hide')) + '</li>'
 
     if admin_check() == 1:
-        data += '''
-            <li>
-                <a href="/history_delete/''' + url_pas(name) + '?num=' + num + '">' + load_lang('history_delete') + '''
-            </li>
-        '''
-
-    data += '''
-        </ul>
-    '''
+        data += '<li><a href="/history_delete/' + url_pas(name) + '?num=' + num + '">' + load_lang('history_delete') + '</li>'
+
+    data += '</ul>'
 
     return easy_minify(flask.render_template(skin_check(),
         imp = [name, wiki_set(), custom(), other2(['(r' + num + ')', 0])],

+ 17 - 12
route/tool/func.py

@@ -8,9 +8,7 @@ for i in range(0, 2):
         import werkzeug.routing
         import werkzeug.debug
         import flask_reggie
-        import tornado.ioloop
-        import tornado.httpserver
-        import tornado.wsgi
+        import wsgiref.simple_server
         import urllib.request
         import email.mime.text
         import sqlite3
@@ -263,7 +261,7 @@ def update(ver_num, set_data):
                 get_data[2]
             ])
 
-    if ver_num < 3200900:
+    if ver_num < 3202100:
         curs.execute(db_change('delete from cache_data'))
 
     conn.commit()
@@ -508,7 +506,7 @@ def next_fix(link, num, page, end = 50):
 
 def other2(data):
     global req_list
-    main_css_ver = '50'
+    main_css_ver = '51'
     data += ['' for _ in range(0, 3 - len(data))]
 
     if req_list == '':
@@ -813,15 +811,17 @@ def load_skin(data = '', set_n = 0, default = 0):
         data = [[data]]
 
     for skin_data in skin_list_get:
+        see_data = skin_data if skin_data != 'default' else load_lang('default')
+
         if not skin_data in system_file:
             if data[0][0] == skin_data:
                 if set_n == 0:
-                    skin_return_data = '<option value="' + skin_data + '">' + skin_data + '</option>' + skin_return_data
+                    skin_return_data = '<option value="' + skin_data + '">' + see_data + '</option>' + skin_return_data
                 else:
                     skin_return_data = [skin_data] + skin_return_data
             else:
                 if set_n == 0:
-                    skin_return_data += '<option value="' + skin_data + '">' + skin_data + '</option>'
+                    skin_return_data += '<option value="' + skin_data + '">' + see_data + '</option>'
                 else:
                     skin_return_data += [skin_data]                    
 
@@ -1104,16 +1104,21 @@ def rd_plus(topic_num, date, name = None, sub = None):
 
     conn.commit()
 
-def history_plus(title, data, date, ip, send, leng, t_check = '', d_type = ''):
-    curs.execute(db_change("select id from history where title = ? and type = '' order by id + 0 desc limit 1"), [title])
-    id_data = curs.fetchall()
-    id_data = str(int(id_data[0][0]) + 1) if id_data else '1'
+def history_plus(title, data, date, ip, send, leng, t_check = '', d_type = '', mode = ''):
+    if mode == 'add':
+        curs.execute(db_change("select id from history where title = ? and type = '' order by id + 0 asc limit 1"), [title])
+        id_data = curs.fetchall()
+        id_data = str(int(id_data[0][0]) - 1) if id_data else '0'
+    else:
+        curs.execute(db_change("select id from history where title = ? and type = '' order by id + 0 desc limit 1"), [title])
+        id_data = curs.fetchall()
+        id_data = str(int(id_data[0][0]) + 1) if id_data else '1'
 
     send = re.sub(r'\(|\)|<|>', '', send)
     send = send[:128] if len(send) > 128 else send
     send = send + ' (' + t_check + ')' if t_check != '' else send
 
-    if not re.search('^user:', title):
+    if not re.search('^user:', title) and mode != 'add':
         curs.execute(db_change("select count(*) from rc where type = 'normal'"))
         if curs.fetchall()[0][0] > 49:
             curs.execute(db_change("select id, title from rc where type = 'normal' order by date asc limit 1"))

+ 4 - 3
route/tool/set_mark/markdown.py

@@ -23,10 +23,11 @@ class head_render:
         self.head_level[head_len_num] += 1
         for i in range(head_len_num + 1, 6):
             self.head_level[i] = 0
-        toc_num_str = str(self.toc_num)
+            
         self.toc_num += 1
-        head_level_str = ('.'.join([str(i) for i in self.head_level]) + '.').replace('0.', '')
-        head_level_str_2 = re.sub(r'\.$', '', head_level_str)
+        toc_num_str = str(self.toc_num)
+        head_level_str_2 = '.'.join([str(i) for i in self.head_level if i != 0])
+        head_level_str = head_level_str_2 + '.'
 
         self.toc_data += '<a href="#s-' + head_level_str_2 + '">' + head_level_str + '</a> ' + head_data + '<br>'
         return '<h' + head_len + ' id="s-' + head_level_str_2 + '"><a href="#toc">' + head_level_str + '</a> ' + head_data + '</h' + head_len + '>'

+ 111 - 165
route/tool/set_mark/namumark.py

@@ -37,7 +37,7 @@ def link_fix(main_link, no_change = 0):
         other_link = ''
 
     main_link = main_link.replace("<link_comma>", "&#x27;")
-    main_link = re.sub(r'\\#', '%23', main_link)
+    main_link = main_link.replace('\\#', '%23')
 
     find_data = re.findall(r'<span id="(nowiki_[0-9]+)">', main_link)
     for i in find_data:
@@ -353,7 +353,7 @@ def middle_parser(data):
 
                         data = re.sub(
                             r'{{{#!wiki(?: style=(?:&quot;|&#x27;)((?:(?!&quot;|&#x27;).)*)(?:&quot;|&#x27;))?(?: *)\n?',
-                            '<div id="wiki_div" style="' + str(middle_data_2[0] if middle_data_2[0] else '') + '">',
+                            '<div_1 style="' + str(middle_data_2[0] if middle_data_2[0] else '') + '">',
                             data,
                             1
                         )
@@ -378,7 +378,7 @@ def middle_parser(data):
                             1
                         )
                     elif re.search(r'^#!folding', middle_data[0]):
-                        middle_list += ['2div']
+                        middle_list += ['div_dd']
 
                         folding_data = re.search(r'{{{#!folding ?((?:(?!\n).)*)\n?', data)
                         if folding_data:
@@ -406,7 +406,7 @@ def middle_parser(data):
                                     '</b>' + \
                                 '</div_2>' + \
                                 '<div id="' + include_name + 'folding_' + str(folding_num) + '" style="display: none;">' + \
-                                    '<div id="wiki_div" style="">',
+                                    '<div_1 style="">\n',
                             data,
                             1
                         )
@@ -443,7 +443,7 @@ def middle_parser(data):
                     if middle_num > 0:
                         middle_num -= 1
 
-                    if middle_list[middle_num] == '2div':
+                    if middle_list[middle_num] == 'div_dd':
                         data = middle_re.sub('</div_1></div_2></div_2>', data, 1)
                     elif middle_list[middle_num] == 'pre':
                         data = middle_re.sub('</code></pre>', data, 1)
@@ -485,10 +485,9 @@ def middle_parser(data):
             nowiki_num += 1
             end_data[include_name + 'nowiki_' + str(nowiki_num)] = nowiki_data[0]
             plus_data += '' + \
-                'if(document.getElementById("' + include_name + 'nowiki_' + str(nowiki_num) + '")) { ' + \
-                    'document.getElementById("' + include_name + 'nowiki_' + str(nowiki_num) + '").innerHTML = "' + nowiki_js(nowiki_data[0]) + '"; ' + \
-                '}' + \
-                '\n' + \
+                'if(document.getElementById("' + include_name + 'nowiki_' + str(nowiki_num) + '")) {\n' + \
+                    'document.getElementById("' + include_name + 'nowiki_' + str(nowiki_num) + '").innerHTML = "' + nowiki_js(nowiki_data[0]) + '";\n' + \
+                '}\n' + \
             ''
 
             data = re.sub(
@@ -511,10 +510,9 @@ def middle_parser(data):
             nowiki_num += 1
             end_data[include_name + 'nowiki_' + str(nowiki_num)] = syntax_data[1]
             plus_data += '' + \
-                'if(document.getElementById("' + include_name + 'nowiki_' + str(nowiki_num) + '")) { ' + \
-                    'document.getElementById("' + include_name + 'nowiki_' + str(nowiki_num) + '").innerHTML = "' + nowiki_js(syntax_data[1]) + '"; ' + \
-                '}' + \
-                '\n' + \
+                'if(document.getElementById("' + include_name + 'nowiki_' + str(nowiki_num) + '")) {\n' + \
+                    'document.getElementById("' + include_name + 'nowiki_' + str(nowiki_num) + '").innerHTML = "' + nowiki_js(syntax_data[1]) + '";\n' + \
+                '}\n' + \
             ''
 
             data = re.sub(
@@ -581,13 +579,10 @@ def namumark(conn, data, title, include_num):
             data = math_re.sub('<span id="math_' + str(first) + '"></span>', data, 1)
 
             plus_data += '' + \
-                'try {' + \
-                    'katex.render(' + \
-                        '"' + nowiki_js(html.unescape(math)) + '",' + \
-                        'document.getElementById(\"' + include_name + 'math_' + str(first) + '\")' + \
-                    ');' + \
-                '} catch {' + \
-                    'document.getElementById(\"' + include_name + 'math_' + str(first) + '\").innerHTML = "<span style=\'color: red;\'>' + nowiki_js(math) + '</span>";' + \
+                'try {\n' + \
+                    'katex.render("' + nowiki_js(html.unescape(math)) + '", document.getElementById(\"' + include_name + 'math_' + str(first) + '\"));\n' + \
+                '} catch {\n' + \
+                    'document.getElementById(\"' + include_name + 'math_' + str(first) + '\").innerHTML = "<span style=\'color: red;\'>' + nowiki_js(math) + '</span>";\n' + \
                 '}\n' + \
             ''
         else:
@@ -602,10 +597,9 @@ def namumark(conn, data, title, include_num):
             nowiki_num += 1
             end_data[include_name + 'nowiki_' + str(nowiki_num)] = one_nowiki[0]
             plus_data += '' + \
-                'if(document.getElementById("' + include_name + 'nowiki_' + str(nowiki_num) + '")) { ' + \
-                    'document.getElementById("' + include_name + 'nowiki_' + str(nowiki_num) + '").innerHTML = "' + nowiki_js(one_nowiki[0]) + '"; ' + \
-                '}' + \
-                '\n' + \
+                'if(document.getElementById("' + include_name + 'nowiki_' + str(nowiki_num) + '")) {\n' + \
+                    'document.getElementById("' + include_name + 'nowiki_' + str(nowiki_num) + '").innerHTML = "' + nowiki_js(one_nowiki[0]) + '";\n' + \
+                '}\n' + \
             ''
 
             data = re.sub(r'(?:\\)(.)', '<span id="' + include_name + 'nowiki_' + str(nowiki_num) + '"></span>', data, 1)
@@ -656,37 +650,32 @@ def namumark(conn, data, title, include_num):
         else:
             break
 
-    data = re.sub(r'\r\n', '\n', data)
-    data = re.sub(r'&amp;', '&', data)
-
-    data = re.sub(r'\n##(((?!\n).)+)', '', data)
-    data = re.sub(r'<div id="wiki_div" style="">\n', '<div id="wiki_div" style="">', data)
+    data = data.replace('&amp;', '&')
+    data = re.sub(r'\n##[^\n]+', '', data)
 
+    div_1_re = re.compile(r'<div_1 ([^>]+)>((?:(?!<div_1|<\/div_1>).|\n*)+)<\/div_1>')
     while 1:
-        wiki_table_data = re.search(r'<div id="wiki_div" ((?:(?!>).)+)>((?:(?!<div id="wiki_div"|<\/div_1>).\n*)+)<\/div_1>', data)
+        wiki_table_data = div_1_re.search(data)
         if wiki_table_data:
             wiki_table_data = wiki_table_data.groups()
             if re.search(r'\|\|', wiki_table_data[1]):
-                end_parser = re.sub(r'\n$', '', re.sub(r'^\n', '', table_start('\n' + wiki_table_data[1] + '\n')))
+                end_parser = table_start('\n' + wiki_table_data[1] + '\n')
+                end_parser = re.sub(r'^\n', '', end_parser)
+                end_parser = re.sub(r'\n$', '', end_parser)
             else:
                 end_parser = wiki_table_data[1]
 
-            data = re.sub(
-                '<div id="wiki_div" ((?:(?!>).)+)>((?:(?!<div id="wiki_div"|<\/div_1>).\n*)+)<\/div_1>',
-                '<div ' + wiki_table_data[0] + '>' + end_parser + '</div_2>', 
-                data, 
-                1
-            )
+            data = div_1_re.sub('<div id="wiki_div" ' + wiki_table_data[0] + '>' + end_parser + '</div_2>', data, 1)
         else:
             break
 
-    data = re.sub(r'<\/div_2>', '</div>', data)
-    data = re.sub(r'<\/td>', '</td_1>', data)
+    data = data.replace('</div_2>', '</div>')
+    data = data.replace('</td>', '</td_1>')
 
     data += '\n'
     data = data.replace('\\', '&#92;')
 
-    redirect_re = re.compile(r'\n#(?:redirect|넘겨주기) ((?:(?!\n).)+)\n', re.I)
+    redirect_re = re.compile(r'\n#(?:redirect|넘겨주기) ([^\n]+)', re.I)
     redirect = redirect_re.search(data)
     if redirect:
         redirect = redirect.group(1)
@@ -697,103 +686,79 @@ def namumark(conn, data, title, include_num):
 
         backlink += [[title, main_link, 'redirect']]
 
-        data = redirect_re.sub(
-            '\n' + \
-                '<ul>' + \
-                    '<li>' + \
-                        '<a id="go_redirect_link" href="/w/' + tool.url_pas(main_link) + other_link + '">' + main_link + other_link + '</a>' + \
-                    '</li>' + \
-                '</ul>' + \
-            '\n',
-            data,
-            1
-        )
+        plus_data += '' + \
+            'var get_link = window.location.search.match(/(?:\?|&)from=([^&]+)/);\n' + \
+            'if(!get_link) {\n' + \
+                'window.location.href = "/w/' + tool.url_pas(main_link) + other_link + '?from=' + tool.url_pas(title) + '";\n' + \
+            '}\n' + \
+        ''
+        data = redirect_re.sub('', data, 1)
 
     no_toc_re = re.compile(r'\[(?:목차|toc)\((?:no)\)\]\n', re.I)
     toc_re = re.compile(r'\[(?:목차|toc)\]', re.I)
     if not no_toc_re.search(data):
         if not toc_re.search(data):
-            data = re.sub(r'\n(?P<in>={1,6}) ?(?P<out>(?:(?!=).)+) ?={1,6}\n', '\n[toc]\n\g<in> \g<out> \g<in>\n', data, 1)
+            data = re.sub(r'(?P<in>\n(={1,6})(#)? ?((?:(?!(?: #=| =)).)+) ?#?(?:=+)\n)', '\n[toc]\g<in>', data, 1)
     else:
         data = no_toc_re.sub('', data)
 
     data = '<div class="all_in_data" id="in_data_0">' + data
 
-    toc_full = 0
-    toc_top_stack = 6
     toc_stack = [0, 0, 0, 0, 0, 0]
-    edit_number = 0
-    toc_data = '<div id="toc"><span id="toc_title">TOC</span>\n\n'
+    toc_num = 0
+    toc_data = '' + \
+        '<div id="toc">' + \
+            '<span id="toc_title">TOC</span>' + \
+            '<br>' + \
+            '<br>' + \
+    ''
+    edit_num = 0
+    toc_head_re = re.compile(r'\n(={1,6})(#)? ?((?:(?!(?: #=| =)).)+) ?#?(?:=+)\n')
     while 1:
-        toc = re.search(r'\n(={1,6}) ?((?:(?!\n).)+) ?(?:={1,6})\n', data)
+        toc = toc_head_re.search(data)
         if toc:
             toc = toc.groups()
+            edit_num += 1
 
-            toc_number = len(toc[0])
-            edit_number += 1
-
-            if toc_full > toc_number:
-                for i in range(toc_number, 6):
-                    toc_stack[i] = 0
-
-            if toc_top_stack > toc_number:
-                toc_top_stack = toc_number
-
-            toc_full = toc_number
-            toc_stack[toc_number - 1] += 1
-            toc_number = str(toc_number)
-            all_stack = ''
-
-            for i in range(0, 6):
-                all_stack += str(toc_stack[i]) + '.'
-
-            while 1:
-                if re.search(r'[^0-9]0\.', all_stack):
-                    all_stack = re.sub(r'[^0-9]0\.', '.', all_stack)
-                else:
-                    break
-
-            all_stack = re.sub(r'^0\.', '', all_stack)
-            all_stack = re.sub(r'\.$', '', all_stack)
+            toc_len_num = len(toc[0])
+            toc_len_str = str(toc_len_num)
+            toc_len_num -= 1
+            toc_stack[toc_len_num] += 1
+            for i in range(toc_len_num + 1, 6):
+                toc_stack[i] = 0
 
-            new_toc_data = re.sub(r'=*$', '', toc[1])
-            new_toc_data = re.sub(r' +$', '', new_toc_data)
-            if re.search(r'^# ?(?P<in>[^#]+) ?#$', new_toc_data):
-                fol_head = '+'
+            edit_num_str = str(edit_num)
+            toc_level_str = '.'.join([str(i) for i in toc_stack if i != 0])
+            toc_fol = '+' if toc[1] else '-'
 
-                new_toc_data = re.sub(r'^# ?(?P<in>[^#]+) ?#$', '\g<in>', new_toc_data)
-            else:
-                fol_head = '-'
-
-            data = re.sub(
-                '\n(={1,6}) ?((?:(?!\n).)+) ?\n',
+            data = toc_head_re.sub(
                 '\n' + \
                 '</div>'
-                '<h' + toc_number + ' id="s-' + all_stack + '">' + \
-                    '<a href="#toc">' + all_stack + '.</a> ' + new_toc_data + ' ' + \
+                '<h' + toc_len_str + ' id="s-' + toc_level_str + '">' + \
+                    '<a href="#toc">' + toc_level_str + '.</a> ' + toc[2] + ' ' + \
                     '<span style="font-size: 12px">' + \
-                        '<a href="/edit/' + tool.url_pas(title) + '?section=' + str(edit_number) + '">(Edit)</a>' + \
+                        '<a href="/edit/' + tool.url_pas(title) + '?section=' + edit_num_str + '">(Edit)</a>' + \
                         ' ' + \
-                        '<a href="javascript:void(0);" onclick="do_open_folding(\'in_data_' + all_stack + '\', this);">' + \
-                            '(' + fol_head + ')' + \
+                        '<a href="javascript:void(0);" onclick="do_open_folding(\'in_data_' + edit_num_str + '\', this);">' + \
+                            '(' + toc_fol + ')' + \
                         '</a>' + \
                     '</span>' + \
-                '</h' + toc_number + '>' + \
-                '<div class="all_in_data"' + (' style="display: none;"' if fol_head == '+' else '') + ' id="in_data_' + all_stack + '">' + \
+                '</h' + toc_len_str + '>' + \
+                '<div class="all_in_data"' + (' style="display: none;"' if toc_fol == '+' else '') + ' id="in_data_' + edit_num_str + '">' + \
                     '\n',
                 data,
                 1
             )
 
-            toc_main_data = new_toc_data
+            toc_main_data = toc[2]
             toc_main_data = re.sub(r'\[\*((?:(?! |\]).)*)(?: ((?:(?!(\[\*(?:(?:(?!\]).)+)\]|\])).)+))?\]', '', toc_main_data)
             toc_main_data = re.sub(r'<span id="math_[0-9]"><\/span>', '(Math)', toc_main_data)
 
             toc_data += '' + \
-                '<span style="margin-left: ' + str((toc_full - toc_top_stack) * 10) + 'px;">' + \
-                    '<a href="#s-' + all_stack + '">' + all_stack + '.</a> ' + toc_main_data + \
+                '<span style="margin-left:' + str(len(re.findall(r'\.', toc_level_str)) * 10) + '">' + \
+                    '<a href="#s-' + toc_level_str + '">' + toc_level_str + '.</a> ' + toc_main_data + \
                 '</span>' + \
-                '\n' + \
+                '<br>' + \
             ''
         else:
             break
@@ -801,10 +766,6 @@ def namumark(conn, data, title, include_num):
     toc_data += '</div>'
     data = toc_re.sub(toc_data, data)
     
-    now_time = tool.get_time()
-    time_data = re.search(r'^([0-9]{4}-[0-9]{2}-[0-9]{2})', now_time)
-    time = time_data.group(1)
-    
     macro_re = re.compile(r'\[([^[(]+)\(((?:(?!\[|\)]).)+)\)\]')
     macro_data = macro_re.findall(data)
     for i in macro_data:
@@ -897,12 +858,7 @@ def namumark(conn, data, title, include_num):
                 if macro_name == 'age':
                     data = macro_re.sub(str(int(e_data.days / 365)), data, 1)
                 else:
-                    if re.search(r'^-', str(e_data.days)):
-                        e_day = str(e_data.days)
-                    else:
-                        e_day = '+' + str(e_data.days)
-
-                    data = macro_re.sub(e_day, data, 1)
+                    data = macro_re.sub((str(e_data.days) if re.search(r'^-', str(e_data.days)) else ('+' + str(e_data.days))), data, 1)
             except:
                 data = macro_re.sub('age-dday-error', data, 1)
         else:
@@ -912,48 +868,40 @@ def namumark(conn, data, title, include_num):
     data = data.replace('<macro_middle>', '(')
     data = data.replace('<macro_end>', ')]')
 
+    blockquote_re = re.compile(r'(\n(?:&gt; ?(?:[^\n]+)\n)+)')
     while 1:
-        block = re.search(r'(\n(?:&gt; ?(?:(?:(?!\n).)+)?\n)+)', data)
-        if block:
-            block = block.group(1)
-
-            block = re.sub(r'^\n&gt; ?', '', block)
-            block = re.sub(r'\n&gt; ?', '\n', block)
-            block = re.sub(r'\n$', '', block)
+        blockquote = blockquote_re.search(data)
+        if blockquote:
+            blockquote = re.sub(r'^\n&gt; ?', '', blockquote.group(1))
+            blockquote = re.sub(r'\n&gt; ?', '\n', blockquote)
+            blockquote = re.sub(r'\n$', '', blockquote)
 
-            data = re.sub(r'(\n(?:&gt; ?(?:(?:(?!\n).)+)?\n)+)', '\n<blockquote>' + block + '</blockquote>\n', data, 1)
+            data = blockquote_re.sub('\n<blockquote>' + blockquote + '</blockquote>\n', data, 1)
         else:
             break
 
+    hr_re = re.compile(r'\n-{4,9}\n')
     while 1:
-        hr = re.search(r'\n-{4,9}\n', data)
-        if hr:
-            data = re.sub(r'\n-{4,9}\n', '\n<hr>\n', data, 1)
+        if hr_re.search(data):
+            data = hr_re.sub('\n<hr>\n', data, 1)
         else:
             break
 
     data = re.sub(r'(?P<in>\n +\* ?(?:(?:(?!\|\|).)+))\|\|', '\g<in>\n ||', data)
     data = re.sub(r'(?P<in><div id="folding_(?:[0-9]+)" style="display: none;"><div style="">|<blockquote>)(?P<out> )?\* ', '\g<in>\n\g<out>* ', data)
 
+    li_re = re.compile(r'(\n(?:(?: +)\* (?:[^\n]+)\n)+)')
     while 1:
-        li = re.search(r'(\n(?:(?: *)\* ?(?:(?:(?!\n).)+)\n)+)', data)
-        if li:
-            li = li.group(1)
-            while 1:
-                sub_li = re.search(r'\n(?:( *)\* ?((?:(?!\n).)+))', li)
-                if sub_li:
-                    sub_li = sub_li.groups()
-
-                    if len(sub_li[0]) == 0:
-                        margin = 20
-                    else:
-                        margin = len(sub_li[0]) * 20
+        li_data = li_re.search(data)
+        if li_data:
+            li_data = li_data.group(1)
+            li_end_data = ''
 
-                    li = re.sub(r'\n(?:( *)\* ?((?:(?!\n).)+))', '<li style="margin-left: ' + str(margin) + 'px;">' + sub_li[1] + '</li>', li, 1)
-                else:
-                    break
+            sub_li = re.findall(r'( +)\* ([^\n]+)\n', li_data)
+            for i in sub_li:
+                li_end_data += '<li style="margin-left: ' + str(20 if len(i[0]) == 0 else (len(i[0]) * 20)) + 'px;">' + i[1] + '</li>'
 
-            data = re.sub(r'(\n(?:(?: *)\* ?(?:(?:(?!\n).)+)\n)+)', '\n\n<ul>' + li + '</ul>\n', data, 1)
+            data = li_re.sub('\n<ul>' + li_end_data + '</ul>\n', data, 1)
         else:
             break
 
@@ -963,11 +911,7 @@ def namumark(conn, data, title, include_num):
     while 1:
         indent = re.search(r'\n( +)', data)
         if indent:
-            indent = len(indent.group(1))
-
-            margin = '<span style="margin-left: 20px;"></span>' * indent
-
-            data = re.sub(r'\n( +)', '\n' + margin, data, 1)
+            data = re.sub(r'\n( +)', '\n' + ('<span style="margin-left: 20px;"></span>' * len(indent.group(1))), data, 1)
         else:
             break
 
@@ -1031,10 +975,16 @@ def namumark(conn, data, title, include_num):
                 else:
                     file_color = ''
 
+                file_alt = re.search(r'alt=((?:(?!&).)+)', see_link)
+                if file_alt:
+                    file_alt = file_alt.group(1)
+                else:
+                    file_alt = ''
+
                 if re.search(r'^(?:out|외부):', main_link):
                     file_src = re.sub(r'^(?:out|외부):', '', main_link)
 
-                    file_alt = main_link
+                    file_alt = main_link if file_alt == '' else file_alt
                     exist = 'Yes'
                 else:
                     file_data = re.search(r'^(?:file|파일):((?:(?!\.).)+)\.(.+)$', main_link)
@@ -1050,7 +1000,7 @@ def namumark(conn, data, title, include_num):
                         file_end = 'jpg'
 
                     file_src = '/image/' + tool.sha224_replace(file_name) + '.' + file_end
-                    file_alt = 'file:' + file_name + '.' + file_end
+                    file_alt = ('file:' + file_name + '.' + file_end) if file_alt == '' else file_alt
                     exist = None
 
                 data = link_re.sub(
@@ -1068,7 +1018,7 @@ def namumark(conn, data, title, include_num):
                 )
             elif category_re.search(main_link):
                 if category == '':
-                    category += '<div id="cate_all"><hr><div id="cate">Category : '
+                    category += '<div id="cate_all"><div id="cate">Category : '
 
                 main_link = category_re.sub('category:', main_link)
                 link_id = ''
@@ -1213,18 +1163,16 @@ def namumark(conn, data, title, include_num):
         plus_data += 'page_count();\n'
         data = re.sub(r'\[pagecount\]', '<span class="all_page_count"></span>', data, flags = re.I)
 
-    data = re.sub(r'\[date\]', now_time, data, flags = re.I)
+    data = re.sub(r'\[date\]', tool.get_time().split()[0], data, flags = re.I)
     data = re.sub(r'\[clearfix\]', '<div style="clear:both"></div>', data, flags = re.I)
     data = re.sub(r'\[br\]', '<br>', data, flags = re.I)
 
+    # 각주 기능
     footnote_number = 0
-
     footnote_all = []
     footnote_dict = {}
     footnote_re = {}
-
-    footdata_all = '<hr><ul id="footnote_data">'
-
+    footdata_all = '<ul id="footnote_data">'
     re_footnote = re.compile(r'(?:\[\*((?:(?! |\]).)*)(?: ((?:(?!(?:\[\*|\])).)+))?\]|(\[(?:각주|footnote)\]))')
     while 1:
         footnote = re_footnote.search(data)
@@ -1251,7 +1199,7 @@ def namumark(conn, data, title, include_num):
                 data = re_footnote.sub(footdata_all + '</ul>', data, 1)
 
                 footnote_all = []
-                footdata_all = '<hr><ul id="footnote_data">'
+                footdata_all = '<ul id="footnote_data">'
             else:
                 footnote = footnote_data[1]
                 footnote_name = footnote_data[0]
@@ -1320,12 +1268,12 @@ def namumark(conn, data, title, include_num):
         ''
 
     footdata_all += '</ul>'
-    footdata_all = '</div>' + footdata_all
-    if footdata_all == '</div><hr><ul id="footnote_data"></ul>':
-        footdata_all = '</div>'
+    footdata_all = '' if footdata_all == '<ul id="footnote_data"></ul>' else footdata_all
+    footdata_all = '</div>' + footdata_all    
 
-    data = re.sub(r'\n$', footdata_all, data + '\n', 1)
+    data += footdata_all
 
+    # 기본 꾸미기 문법
     data = re.sub(r'&#x27;&#x27;&#x27;(?P<in>((?!&#x27;&#x27;&#x27;).)+)&#x27;&#x27;&#x27;', '<b>\g<in></b>', data)
     data = re.sub(r'&#x27;&#x27;(?P<in>((?!&#x27;&#x27;).)+)&#x27;&#x27;', '<i>\g<in></i>', data)
     data = re.sub(r'~~(?P<in>(?:(?!~~).)+)~~', '<s>\g<in></s>', data)
@@ -1334,11 +1282,8 @@ def namumark(conn, data, title, include_num):
     data = re.sub(r'\^\^(?P<in>(?:(?!\^\^).)+)\^\^', '<sup>\g<in></sup>', data)
     data = re.sub(r',,(?P<in>(?:(?!,,).)+),,', '<sub>\g<in></sub>', data)
 
-    if category != '':
-        category = re.sub(r' \| $', '', category) + '</div></div>'
-
-    data += category
-
+    # 최종 처리
+    data += (re.sub(r' \| $', '', category) + '</div></div>') if category != '' else ''
     data = data.replace('<no_table>', '||')
     data = data.replace('</td_1>', '</td>')
     data = re.sub(r'<\/ul>\n?', '</ul>', data)
@@ -1347,7 +1292,7 @@ def namumark(conn, data, title, include_num):
     data = data.replace('\n\n<ul>', '\n<ul>')
     data = data.replace('</ul>\n\n', '</ul>')
     data = re.sub(r'^(\n)+', '', data)
-    data = re.sub(r'(\n)+<hr><ul id="footnote_data">', '<hr><ul id="footnote_data">', data)
+    data = re.sub(r'(\n)+<ul id="footnote_data">', '<ul id="footnote_data">', data)
     data = re.sub(r'(?P<in><td(((?!>).)*)>)\n', '\g<in>', data)
     data = re.sub(r'(\n)?<hr>(\n)?', '<hr>', data)
     data = data.replace('</ul>\n\n<ul>', '</ul>\n<ul>')
@@ -1355,6 +1300,7 @@ def namumark(conn, data, title, include_num):
     data = data.replace('\n</ul>', '</ul>')
     data = data.replace('\n', '<br>')
 
+    # 파일, 링크, HTML 작동 JS
     plus_data += '' + \
         'get_link_state("' + include_name + '");\n' + \
         'get_file_state("' + include_name + '");\n' + \

+ 1 - 1
route/tool/set_mark/tool.py

@@ -21,7 +21,7 @@ def db_change(data):
 
 def ip_check(d_type = 0):
     ip = ''
-    if d_type == 0 and (flask.session and ('state' and 'id') in flask.session):
+    if d_type == 0 and (flask.session and 'id' in flask.session):
         ip = flask.session['id']
     
     if ip == '':

+ 41 - 9
route/user_setting.py

@@ -11,15 +11,30 @@ def user_setting_2(conn, server_init):
 
     if ip_or_user(ip) == 0:
         if flask.request.method == 'POST':
-            auto_list = ['email', 'skin', 'lang']
+            pass_list = ['2fa']
+            auto_list = ['skin', 'lang'] + pass_list + ['2fa_pw', '2fa_pw_encode']
 
             for auto_data in auto_list:
-                if flask.request.form.get(auto_data, '') != '':
+                if auto_data == '2fa_pw':
+                    if flask.request.form.get('2fa_pw', '') != '':
+                        get_data = pw_encode(flask.request.form.get(auto_data, ''))
+                    else:
+                        get_data = ''
+                elif auto_data == '2fa_pw_encode':
+                    if flask.request.form.get('2fa_pw', '') != '':
+                        curs.execute(db_change("select encode from user where id = ?"), [ip])
+                        get_data = curs.fetchall()[0][0]
+                    else:
+                        get_data = ''
+                else:
+                    get_data = flask.request.form.get(auto_data, '')
+
+                if auto_data in pass_list or get_data != '':
                     curs.execute(db_change('select data from user_set where name = ? and id = ?'), [auto_data, ip])
                     if curs.fetchall():
-                        curs.execute(db_change("update user_set set data = ? where name = ? and id = ?"), [flask.request.form.get(auto_data, ''), auto_data, ip])
+                        curs.execute(db_change("update user_set set data = ? where name = ? and id = ?"), [get_data, auto_data, ip])
                     else:
-                        curs.execute(db_change("insert into user_set (name, id, data) values (?, ?, ?)"), [auto_data, ip, flask.request.form.get(auto_data, '')])
+                        curs.execute(db_change("insert into user_set (name, id, data) values (?, ?, ?)"), [auto_data, ip, get_data])
 
             conn.commit()
 
@@ -41,23 +56,36 @@ def user_setting_2(conn, server_init):
                 data = [['default']]
 
             for lang_data in support_language:
+                see_data = lang_data if lang_data != 'default' else load_lang('default')
+                
                 if data and data[0][0] == lang_data:
-                    div3 = '<option value="' + lang_data + '">' + lang_data + '</option>' + div3
+                    div3 = '<option value="' + lang_data + '">' + see_data + '</option>' + div3
                 else:
-                    div3 += '<option value="' + lang_data + '">' + lang_data + '</option>'
+                    div3 += '<option value="' + lang_data + '">' + see_data + '</option>'
 
-            http_warring = '<hr class="main_hr"><span>' + load_lang('http_warring') + '</span>'
+            curs.execute(db_change('select data from user_set where name = "2fa" and id = ?'), [ip])
+            fa_data = curs.fetchall()
+            fa_data = 'checked' if fa_data and fa_data[0][0] != '' else ''
+
+            curs.execute(db_change('select data from user_set where name = "2fa_pw" and id = ?'), [ip])
+            fa_data_pw = curs.fetchall()
+            fa_data_pw = load_lang('2fa_password_change') if fa_data_pw else load_lang('2fa_password')
+            
+            http_warring = '' + \
+                '<hr class="main_hr">' + \
+                '<span>' + load_lang('http_warring') + '</span>' + \
+            ''
 
             return easy_minify(flask.render_template(skin_check(),
                 imp = [load_lang('user_setting'), wiki_set(), custom(), other2([0, 0])],
                 data = '''
                     <form method="post">
-                        <span>''' + load_lang('id') + ''' : ''' + ip + '''</span>
+                        <span>''' + load_lang('id') + ''' : ''' + ip_pas(ip) + '''</span>
                         <hr class="main_hr">
                         <a href="/pw_change">(''' + load_lang('password_change') + ''')</a>
                         <hr class="main_hr">
                         <span>''' + load_lang('email') + ''' : ''' + email + '''</span> <a href="/email_change">(''' + load_lang('email_change') + ''')</a>
-                        <hr class="main_hr">
+                        <h2>''' + load_lang('main') + '''</h2>
                         <span>''' + load_lang('skin') + '''</span>
                         <hr class="main_hr">
                         <select name="skin">''' + div2 + '''</select>
@@ -65,6 +93,10 @@ def user_setting_2(conn, server_init):
                         <span>''' + load_lang('language') + '''</span>
                         <hr class="main_hr">
                         <select name="lang">''' + div3 + '''</select>
+                        <h2>''' + load_lang('2fa') + '''</h2>
+                        <input type="checkbox" name="2fa" value="on" ''' + fa_data + '''> ''' + load_lang('on') + '''
+                        <hr class="main_hr">
+                        <input type="password" name="2fa_pw" placeholder="''' + fa_data_pw + '''">
                         <hr class="main_hr">
                         <button type="submit">''' + load_lang('save') + '''</button>
                         ''' + http_warring + '''

+ 0 - 3
route/view_read.py

@@ -11,9 +11,6 @@ def view_read_2(conn, name):
     num = flask.request.args.get('num', None)
     if num:
         num = int(number_check(num))
-    else:
-        if not flask.request.args.get('from', None):
-            run_redirect = '<script>not_from_exist();</script>'
 
     curs.execute(db_change("select sub from rd where title = ? and not stop = 'O' order by date desc"), [name])
     if curs.fetchall():

+ 3 - 3
version.json

@@ -1,11 +1,11 @@
 {
     "master" : {
-        "r_ver" : "v3.2.0-master-20",
-        "c_ver" : "3201601",
+        "r_ver" : "v3.2.0-master-21 (v3.2.0-dev-202008010-01)",
+        "c_ver" : "3202100",
         "s_ver" : "9"
     }, "stable" : {
         "r_ver" : "v3.2.0-stable-11",
         "c_ver" : "3200900",
         "s_ver" : "9"
     }
-}
+}

+ 1 - 0
views/main_css/css/main.css

@@ -49,3 +49,4 @@ blockquote { background-image: url(/views/main_css/file/quote.png); background-p
 .content { height: 500px; }
 .topic_content { height: 200px; }
 .spead_footnote { background-color: #efefef; color: #555; border: 1px solid #cecece; }
+#footnote_data { border-top: 1px solid gainsboro; padding-top: 10px; }

+ 0 - 16
views/main_css/js/load_namumark.js

@@ -134,22 +134,6 @@ function page_count() {
     }
 }
 
-function not_from_exist() {
-    window.addEventListener('DOMContentLoaded', function() {
-        if(document.getElementById('go_redirect_link')) {
-            var r_link = document.getElementById('go_redirect_link').href;
-            if(r_link.match(/#([^#]+)$/)) {
-                var s_link = '#' + r_link.match(/#([^#]+)$/)[1];
-                r_link = r_link.replace(/#([^#]+)$/, '');
-            } else {
-                var s_link = '';
-            }
-
-            window.location.href = r_link + '?from=' + location.pathname.replace(/^\/w\//, '') + s_link;
-        }
-    });
-}
-
 function do_open_folding(data, element = '') {
     var fol = document.getElementById(data);
     if(fol.style.display === '' || (fol.style.display === 'inline-block' || fol.style.display === 'block')) {