1# Copyright (C) 2019 The Android Open Source Project 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15import flask 16import logging 17import os 18import re 19import requests 20import time 21import urllib.parse 22 23from collections import namedtuple 24from config import GERRIT_HOST, GERRIT_PROJECT 25''' Makes anonymous GET-only requests to Gerrit. 26 27Solves the lack of CORS headers from AOSP gerrit. 28''' 29 30HASH_RE = re.compile('^[a-f0-9]+$') 31CACHE_TTL = 3600 # 1 h 32CacheEntry = namedtuple('CacheEntry', ['contents', 'expiration']) 33 34app = flask.Flask(__name__) 35 36logging.basicConfig( 37 format='%(levelname)-8s %(asctime)s %(message)s', 38 level=logging.DEBUG if os.getenv('VERBOSE') else logging.INFO, 39 datefmt=r'%Y-%m-%d %H:%M:%S') 40 41cache = {} 42 43 44def DeleteStaleCacheEntries(): 45 now = time.time() 46 for url, entry in list(cache.items()): 47 if now > entry.expiration: 48 cache.pop(url, None) 49 50 51def req_cached(url): 52 '''Used for requests that return immutable data, avoid hitting Gerrit 500''' 53 DeleteStaleCacheEntries() 54 entry = cache.get(url) 55 contents = entry.contents if entry is not None else None 56 if not contents: 57 resp = requests.get(url) 58 if resp.status_code != 200: 59 err_str = 'http error %d while fetching %s' % (resp.status_code, url) 60 return resp.status_code, err_str 61 contents = resp.content.decode('utf-8') 62 cache[url] = CacheEntry(contents, time.time() + CACHE_TTL) 63 return contents, 200 64 65 66@app.route('/gerrit/commits/<string:sha1>', methods=['GET', 'POST']) 67def commits(sha1): 68 if not HASH_RE.match(sha1): 69 return 'Malformed input', 500 70 project = urllib.parse.quote(GERRIT_PROJECT, '') 71 url = 'https://%s/projects/%s/commits/%s' % (GERRIT_HOST, project, sha1) 72 content, status = req_cached(url) 73 return content[4:], status # 4: -> Strip Gerrit XSSI chars. 74 75 76@app.route( 77 '/gerrit/log/<string:first>..<string:second>', methods=['GET', 'POST']) 78def gerrit_log(first, second): 79 if not HASH_RE.match(first) or not HASH_RE.match(second): 80 return 'Malformed input', 500 81 url = 'https://%s/%s/+log/%s..%s?format=json' % (GERRIT_HOST.replace( 82 '-review', ''), GERRIT_PROJECT, first, second) 83 content, status = req_cached(url) 84 return content[4:], status # 4: -> Strip Gerrit XSSI chars. 85 86 87@app.route('/gerrit/changes/', methods=['GET', 'POST']) 88def gerrit_changes(): 89 url = 'https://%s/changes/?q=project:%s+' % (GERRIT_HOST, GERRIT_PROJECT) 90 url += flask.request.query_string.decode('utf-8') 91 resp = requests.get(url) 92 hdr = {'Content-Type': 'text/plain'} 93 status = resp.status_code 94 if status == 200: 95 resp = resp.content.decode('utf-8')[4:] # 4: -> Strip Gerrit XSSI chars. 96 else: 97 resp = 'HTTP error %s' % status 98 return resp, status, hdr 99