Repository: LMFDB/lmfdb

Issue:
Limit download_qexp requests for elliptic curves
Requests like

https://www.lmfdb.org/EllipticCurve/Q/download_qexp/37.a1/9000000

(which showed up in the flasklog) put a heavy CPU load on the server and eventually lead to a 502.  I suggest capping these at 10^5.


Discussion:
I would be even harsher. This is *not* a download of stored data (we only store ap for p<100) but a request to compute ap.  It should probably be disabled.  Anyone in a position to use these ap would easily be able to compute them.
@JohnCremona I think we want to keep the download q-exp link, which gives the first 1000 an.  We could just remove the bound and always make it give 1000, but this would be less than the number we provide for the corresponding modular forms for N up to 10000 (we provide 3000), which seems a little weird.  For CMFs we always provide at least 30*sqrt(N), which matches the number of a_n we use when computing the L-function.  Allowing up to 10^5 would be well above this and still takes less time than it takes to render the page.
OK fair enough -- but then can we open a new issue to increase the number if ap and an stored?
I think this is the rare case where I would argue that its OK to compute them on the fly, since we are never searching on them and it can be done quickly and reliably.  @edgarcosta do you have an opinion about this?
I agree with you.
Indeed, is impressive how fast we return
https://www.lmfdb.org/EllipticCurve/Q/download_qexp/37.a1/10000

On Thu, Oct 29, 2020 at 5:17 AM Andrew Sutherland <notifications@github.com>
wrote:

> I think this is the rare case where I would argue that its OK to compute
> them on the fly, since we are never searching on them and it can be done
> quickly and reliably. @edgarcosta <https://github.com/edgarcosta> do you
> have an opinion about this?
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/LMFDB/lmfdb/issues/4194#issuecomment-718523002>, or
> unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AACO2BTYBT3SEUMWSICHJJLSNEXILANCNFSM4TCER4WA>
> .
>

I submit! With the sensible cap of course.

Resolving patch:
diff --git a/lmfdb/elliptic_curves/elliptic_curve.py b/lmfdb/elliptic_curves/elliptic_curve.py
--- a/lmfdb/elliptic_curves/elliptic_curve.py
+++ b/lmfdb/elliptic_curves/elliptic_curve.py
@@ -585,13 +585,15 @@ def padic_data():
     return render_template("ec-padic-data.html", info=info)
 
 
-@ec_page.route("/download_qexp/<label>/<limit>")
+@ec_page.route("/download_qexp/<label>/<int:limit>")
 def download_EC_qexp(label, limit):
     N, iso, number = split_lmfdb_label(label)
     if number:
         ainvs = db.ec_curves.lookup(label, 'ainvs', 'lmfdb_label')
     else:
         ainvs = db.ec_curves.lookup(label, 'ainvs', 'lmfdb_iso')
+    if limit > 100000:
+        return redirect(url_for('.download_EC_qexp',label=label,limit=10000), 301)
     E = EllipticCurve(ainvs)
     response = make_response(','.join(str(an) for an in E.anlist(int(limit), python_ints=True)))
     response.headers['Content-type'] = 'text/plain'
diff --git a/lmfdb/lfunctions/main.py b/lmfdb/lfunctions/main.py
--- a/lmfdb/lfunctions/main.py
+++ b/lmfdb/lfunctions/main.py
@@ -417,13 +417,13 @@ def l_function_hgm_page(label,t):
     return render_single_Lfunction(HypergeometricMotiveLfunction, args, request)
 
 # L-function of symmetric powers of Elliptic curve #############################
-@l_function_page.route("/SymmetricPower/<power>/EllipticCurve/Q/<conductor>/<isogeny>/")
+@l_function_page.route("/SymmetricPower/<int:power>/EllipticCurve/Q/<int:conductor>/<isogeny>/")
 def l_function_ec_sym_page(power, conductor, isogeny):
     args = {'power': power, 'underlying_type': 'EllipticCurve', 'field': 'Q',
             'conductor': conductor, 'isogeny': isogeny}
     return render_single_Lfunction(SymmetricPowerLfunction, args, request)
 
-@l_function_page.route("/SymmetricPower/<power>/EllipticCurve/Q/<label>/")
+@l_function_page.route("/SymmetricPower/<int:power>/EllipticCurve/Q/<label>/")
 def l_function_ec_sym_page_label(power, label):
     conductor, isogeny = getConductorIsogenyFromLabel(label)
     if conductor and isogeny:
@@ -773,6 +773,82 @@ def set_navi(L):
         return ( prev_data, next_data )
 
 
+def generateLfunctionFromUrl(*args, **kwds):
+    ''' Returns the L-function object corresponding to the supplied argumnents
+    from the url. kwds contains possible arguments after a question mark.
+    '''
+    if args[0] == 'Riemann':
+        return RiemannZeta()
+
+    elif args[0] == 'Character' and args[1] == 'Dirichlet':
+        return Lfunction_Dirichlet(charactermodulus=args[2], characternumber=args[3])
+
+    elif args[0] == 'EllipticCurve' and args[1] == 'Q':
+        #return Lfunction_EC_Q(conductor=args[2], isogeny=args[3])
+        return Lfunction_EC(field_label="1.1.1.1", conductor_label=args[2], isogeny_class_label=args[3])
+    elif args[0] == 'EllipticCurve' and args[1] != 'Q':
+        return Lfunction_EC(field_label=args[1], conductor_label=args[2], isogeny_class_label=args[3])
+
+    elif args[0] == 'ModularForm' and args[1] == 'GL2' and args[2] == 'Q' and args[3] == 'holomorphic':  # this has args: one for weight and one for level
+        if len(args) == 10:
+            return Lfunction_CMF(level=args[4], weight=args[5], char_orbit_label=args[6], hecke_orbit=args[7], character=args[8], number=args[9])
+        else:
+            return Lfunction_CMF_orbit(level=args[4], weight=args[5], char_orbit_label=args[6], hecke_orbit=args[7])
+
+    elif args[0] == 'ModularForm' and args[1] == 'GL2' and args[2] == 'TotallyReal' and args[4] == 'holomorphic':  # Hilbert modular form
+        instance = db.lfunc_instances.lucky({'url': hmf_url(args[5], args[6], args[7])})
+        return Lfunction_HMFDB(label=args[5], character=args[6], number=args[7]) if instance else Lfunction_HMF(label=args[5], character=args[6], number=args[7])
+
+    elif args[0] == 'ModularForm' and args[1] == 'GL2' and args[2] == 'ImaginaryQuadratic':  # Bianchi modular form
+        return Lfunction_BMF(field=args[3], level=args[4], suffix=args[5])
+
+    elif args[0] == 'ModularForm' and args[1] == 'GL2' and args[2] == 'Q' and args[3] == 'Maass':
+        maass_id = args[4]
+        return Lfunction_Maass(maass_id = maass_id, fromDB = False)
+
+    elif args[0] == 'ModularForm' and (args[1] == 'GSp4' or args[1] == 'GL4' or args[1] == 'GL3') and args[2] == 'Q' and args[3] == 'Maass':
+        return Lfunction_Maass(fromDB = True, group = args[1], level = args[4],
+                char = args[5], R = args[6], ap_id = args[7])
+
+    elif args[0] == 'ModularForm' and args[1] == 'GSp' and args[2] == 'Q' and args[3] == 'Sp4Z':
+        return Lfunction_SMF2_scalar_valued(weight=args[4], orbit=args[5], number=args[6])
+
+    elif args[0] == 'NumberField':
+        return DedekindZeta(label=str(args[1]))
+
+    elif args[0] == "ArtinRepresentation":
+        label = args[1]
+        from lmfdb.artin_representations.main import both_labels
+        for elt in both_labels(label):
+            if db.lfunc_instances.lucky({'type':'Artin','url': artin_url(elt)}):
+                label = elt
+                return ArtinLfunctionDB(label=label)
+        else:
+            label = parse_artin_label(label, safe=True)
+            return ArtinLfunction(label=label)
+
+    elif args[0] == "SymmetricPower":
+        return SymmetricPowerLfunction(power=args[1], underlying_type=args[2], field=args[3],
+                                       conductor=args[4], isogeny = args[5])
+
+    elif args[0] == "Motive" and args[1] == "Hypergeometric" and args[2] == "Q":
+        if args[4]:
+            return HypergeometricMotiveLfunction(family = args[3], t = args[4])
+        else:
+            return HypergeometricMotiveLfunction(label = args[3])
+
+    elif args[0] == "Genus2Curve" and args[1] == "Q":
+        return Lfunction_genus2_Q(label=str(args[2])+'.'+str(args[3]))
+
+
+    elif args[0] in ['lhash', 'Lhash']:
+        return Lfunction_from_db(Lhash=str(args[1]))
+
+    elif is_debug_mode():
+        raise Exception
+    else:
+        return None
+
 ################################################################################
 #   Route functions, plotting L-function and displaying zeros
 ################################################################################
@@ -797,24 +873,42 @@ def zerosLfunction(args):
 @l_function_page.route("/download_euler/<path:args>/")
 def download_euler(args):
     args = tuple(args.split('/'))
-    return generateLfunctionFromUrl(*args).download_euler_factors()
+    try:
+        L = generateLfunctionFromUrl(*args)
+        assert L
+    except:
+        return abort(404)
+    return L.download_euler_factors()
 
 @l_function_page.route("/download_zeros/<path:args>/")
 def download_zeros(args):
     args = tuple(args.split('/'))
-    return generateLfunctionFromUrl(*args).download_zeros()
+    try:
+        L = generateLfunctionFromUrl(*args)
+        assert L
+    except:
+        return abort(404)
+    return L.download_zeroes()
 
 @l_function_page.route("/download_dirichlet_coeff/<path:args>/")
 def download_dirichlet_coeff(args):
     args = tuple(args.split('/'))
-    return generateLfunctionFromUrl(*args).download_dirichlet_coeff()
+    try:
+        L = generateLfunctionFromUrl(*args)
+        assert L
+    except:
+        return abort(404)
+    return L.download_dirichlet_coeff()
 
 @l_function_page.route("/download/<path:args>/")
 def download(args):
     args = tuple(args.split('/'))
-    return generateLfunctionFromUrl(*args).download()
-
-
+    try:
+        L = generateLfunctionFromUrl(*args)
+        assert L
+    except:
+        return abort(404)
+    return L.download()
 
 
 ################################################################################
@@ -838,9 +932,12 @@ def render_plotLfunction(request, *args):
 
 
 def getLfunctionPlot(request, *args):
-    pythonL = generateLfunctionFromUrl(*args, **to_dict(request.args))
-    if not pythonL:
+    try:
+        pythonL = generateLfunctionFromUrl(*args, **to_dict(request.args))
+        assert pythonL
+    except:
         return ""
+
     plotrange = 30
     if hasattr(pythonL, 'plotpoints'):
         F = p2sage(pythonL.plotpoints)
@@ -896,9 +993,7 @@ def render_zerosLfunction(request, *args):
     try:
         L = generateLfunctionFromUrl(*args, **to_dict(request.args))
     except Exception as err:
-        raise
-        if not is_debug_mode():
-            return render_lfunction_exception(err)
+        return render_lfunction_exception(err)
 
     if not L:
         return abort(404)
@@ -944,83 +1039,6 @@ def render_zerosLfunction(request, *args):
      #   negativeZeros[1:len(negativeZeros) - 1], positiveZeros[1:len(positiveZeros) - 1])
         negativeZeros.replace("-","&minus;"), positiveZeros)
 
-
-def generateLfunctionFromUrl(*args, **kwds):
-    ''' Returns the L-function object corresponding to the supplied argumnents
-    from the url. kwds contains possible arguments after a question mark.
-    '''
-    if args[0] == 'Riemann':
-        return RiemannZeta()
-
-    elif args[0] == 'Character' and args[1] == 'Dirichlet':
-        return Lfunction_Dirichlet(charactermodulus=args[2], characternumber=args[3])
-
-    elif args[0] == 'EllipticCurve' and args[1] == 'Q':
-        #return Lfunction_EC_Q(conductor=args[2], isogeny=args[3])
-        return Lfunction_EC(field_label="1.1.1.1", conductor_label=args[2], isogeny_class_label=args[3])
-    elif args[0] == 'EllipticCurve' and args[1] != 'Q':
-        return Lfunction_EC(field_label=args[1], conductor_label=args[2], isogeny_class_label=args[3])
-
-    elif args[0] == 'ModularForm' and args[1] == 'GL2' and args[2] == 'Q' and args[3] == 'holomorphic':  # this has args: one for weight and one for level
-        if len(args) == 10:
-            return Lfunction_CMF(level=args[4], weight=args[5], char_orbit_label=args[6], hecke_orbit=args[7], character=args[8], number=args[9])
-        else:
-            return Lfunction_CMF_orbit(level=args[4], weight=args[5], char_orbit_label=args[6], hecke_orbit=args[7])
-
-    elif args[0] == 'ModularForm' and args[1] == 'GL2' and args[2] == 'TotallyReal' and args[4] == 'holomorphic':  # Hilbert modular form
-        instance = db.lfunc_instances.lucky({'url': hmf_url(args[5], args[6], args[7])})
-        return Lfunction_HMFDB(label=args[5], character=args[6], number=args[7]) if instance else Lfunction_HMF(label=args[5], character=args[6], number=args[7])
-
-    elif args[0] == 'ModularForm' and args[1] == 'GL2' and args[2] == 'ImaginaryQuadratic':  # Bianchi modular form
-        return Lfunction_BMF(field=args[3], level=args[4], suffix=args[5])
-
-    elif args[0] == 'ModularForm' and args[1] == 'GL2' and args[2] == 'Q' and args[3] == 'Maass':
-        maass_id = args[4]
-        return Lfunction_Maass(maass_id = maass_id, fromDB = False)
-
-    elif args[0] == 'ModularForm' and (args[1] == 'GSp4' or args[1] == 'GL4' or args[1] == 'GL3') and args[2] == 'Q' and args[3] == 'Maass':
-        return Lfunction_Maass(fromDB = True, group = args[1], level = args[4],
-                char = args[5], R = args[6], ap_id = args[7])
-
-    elif args[0] == 'ModularForm' and args[1] == 'GSp' and args[2] == 'Q' and args[3] == 'Sp4Z':
-        return Lfunction_SMF2_scalar_valued(weight=args[4], orbit=args[5], number=args[6])
-
-    elif args[0] == 'NumberField':
-        return DedekindZeta(label=str(args[1]))
-
-    elif args[0] == "ArtinRepresentation":
-        label = args[1]
-        from lmfdb.artin_representations.main import both_labels
-        for elt in both_labels(label):
-            if db.lfunc_instances.lucky({'type':'Artin','url': artin_url(elt)}):
-                label = elt
-                return ArtinLfunctionDB(label=label)
-        else:
-            label = parse_artin_label(label, safe=True)
-            return ArtinLfunction(label=label)
-
-    elif args[0] == "SymmetricPower":
-        return SymmetricPowerLfunction(power=args[1], underlying_type=args[2], field=args[3],
-                                       conductor=args[4], isogeny = args[5])
-
-    elif args[0] == "Motive" and args[1] == "Hypergeometric" and args[2] == "Q":
-        if args[4]:
-            return HypergeometricMotiveLfunction(family = args[3], t = args[4])
-        else:
-            return HypergeometricMotiveLfunction(label = args[3])
-
-    elif args[0] == "Genus2Curve" and args[1] == "Q":
-        return Lfunction_genus2_Q(label=str(args[2])+'.'+str(args[3]))
-
-
-    elif args[0] in ['lhash', 'Lhash']:
-        return Lfunction_from_db(Lhash=str(args[1]))
-
-    elif is_debug_mode():
-        raise Exception
-    else:
-        return None
-
 ################################################################################
 #   Route functions, graphs for browsing L-functions
 ################################################################################
@@ -1248,7 +1266,11 @@ def processSymPowerEllipticCurveNavigation(startCond, endCond, power):
 def reliability(prepath):
     t = 'Reliability of L-function data'
     args = tuple(prepath.split('/'))
-    L = generateLfunctionFromUrl(*args)
+    try:
+        L = generateLfunctionFromUrl(*args)
+        assert L
+    except:
+        return abort(404)
     info={'bread': ()}
     set_bread_and_friends(info, L, request)
     if L.fromDB:
@@ -1277,7 +1299,11 @@ def completeness():
 def source(prepath):
     t = 'Source of L-function data'
     args = tuple(prepath.split('/'))
-    L = generateLfunctionFromUrl(*args)
+    try:
+        L = generateLfunctionFromUrl(*args)
+        assert L
+    except:
+        return abort(404)
     info={'bread': ()}
     set_bread_and_friends(info, L, request)
     if L.fromDB:


Test patch: