Projet

Général

Profil

Tâche #30534

Scénario #30270: Portage python3 de l'EAD2

Erreur de "décodage" du contenu du fichier téléchargé pour l’import be1d

Ajouté par Benjamin Bohard il y a plus de 3 ans. Mis à jour il y a plus de 3 ans.

Statut:
Fermé
Priorité:
Normal
Assigné à:
Début:
02/06/2020
Echéance:
% réalisé:

100%

Temps estimé:
0.00 h
Restant à faire (heures):
0.0

Description

Lors de téléchargement du fichier, le contenu est converti pour échapper tous les caractères non ascii avec la méthode d’encodage des url.
La fonction unquote considère par défaut que les caractères encodés ainsi sont en utf-8 à l’origine.
Au moment, où la méthode unquote est appelée, on ignore (en théorie) quel encodage est utilisé pour le fichier d’origine.

Révisions associées

Révision a035c5e7 (diff)
Ajouté par Benjamin Bohard il y a plus de 3 ans

Do not use unquote in first step of importation.

Ref #30534

Révision f24e8c37 (diff)
Ajouté par Benjamin Bohard il y a plus de 3 ans

Use unquote with trustworthy information on encoding.

Ref #30534

Révision 0bbc7465 (diff)
Ajouté par Benjamin Bohard il y a plus de 3 ans

Code for è is c3a8 (c3a9 stands for é).

Ref #30534

Révision 7106e9ac (diff)
Ajouté par Benjamin Bohard il y a plus de 3 ans

Raise exception if encoding is unknown.

Ref #30534

Révision a027290b (diff)
Ajouté par Daniel Dehennin il y a plus de 3 ans

The urllib quote/unquote is not symmetric in python3

The urllib “quote” encode files differently if they are encoded
“ISO-8859-1” and “UTF-8” but “unquote” assume it was “UTF-8” by
default.

We need to use a symmetric encoding/decoding without taking care of
charset, we switch to base64.

  • frontend/web/page.py (ConnectedPage.locateChild): send uploaded file
    through XMLRPC as base64 encoded.
  • backend/actions/lib/importation/fichier.py (BaseImportationFichier._file_infos):
    decode uploaded file as base64 encoded.
  • backend/actions/lib/importation/tools.py (write_file): write content
    as bytes.

Ref: #30534

Révision 018a51e1 (diff)
Ajouté par Daniel Dehennin il y a plus de 3 ans

be1d: decoding of uploaded file is done by EAD

  • scribe/parsing/be1d.py (parse_be1d_eleves): do only the file convert
    if required.

Ref: #30534

Historique

#1 Mis à jour par Benjamin Bohard il y a plus de 3 ans

En conservant le plus longtemps possible le contenu échappé et en appelant la méthode unquote au dernier moment, lorsqu’on sait qu’on cherche les en-têtes du fichier be1d, on peut déterminer l’encodage avec plus d’assurance.

#2 Mis à jour par Benjamin Bohard il y a plus de 3 ans

  • Statut changé de Nouveau à En cours

#3 Mis à jour par Joël Cuissinat il y a plus de 3 ans

C'est mieux, mais le test est toujours en erreur :
https://dev-eole.ac-dijon.fr/jenkins/job/2.8.0/job/test-importation-acascribe-special-2.8.0-amd64/13/console

            INFO:scribe.importation:## Lecture des élèves... ##
            ERROR:scribe.importation:local variable 'encoding' referenced before assignment

            AUTOMATE : Traceback dans la sortie console!

            Traceback (most recent call last):
              File "/usr/share/ead2/backend/bin/importation.py", line 257, in do_parse_be1d
                be1d.parse_be1d_eleves(self.store, eleve)
              File "/usr/lib/python3/dist-packages/scribe/parsing/be1d.py", line 49, in parse_be1d_eleves
                unquoted_csv = unquote(quoted_csv, encoding=encoding)
            UnboundLocalError: local variable 'encoding' referenced before assignment

#4 Mis à jour par Daniel Dehennin il y a plus de 3 ans

Je pense que l’utilisation de chardet pourrait rendre le code plus sexy et prendre en compte plus de cas du coup :

diff --git a/scribe/parsing/be1d.py b/scribe/parsing/be1d.py
index d0561b1..2ddf9aa 100644
--- a/scribe/parsing/be1d.py
+++ b/scribe/parsing/be1d.py
@@ -16,6 +16,8 @@
 from csv import DictReader, reader
 from urllib.parse import unquote
 from sqlalchemy import and_
+from chardet import detect
+
 from scribe.eoletools import replace_cars, convert_file, formate_date, \
 formate_civilite, replace_more_cars
 from scribe.importation import log
@@ -34,23 +36,22 @@ def parse_be1d_eleves(store, csvfile):
     """ 
     num = 0
     log.infolog("Lecture des élèves...", title=True)
-    # fichier en UTF-8 ?
-    # le caractère è est encodé avec %C3%A9 en utf-8, %E8 en iso-8859-1
-    # ce qui se trouve entre le premier l et le premier v permet de déduire l’encodage.
-    with open(csvfile, 'r') as csvstream:
-        quoted_csv = csvstream.read()
-    first_l_index = quoted_csv.index('l')
-    first_v_index = quoted_csv.index('v')
-    marker = quoted_csv[first_l_index + 1: first_v_index].upper()
-    if marker == '%C3%A8':
-        encoding = 'utf-8'
-    elif marker == '%E8':
-        encoding = 'iso-8859-1'
-    else:
-        raise Exception("L’encodage du fichier source n’a pas pu être déterminé")
-    unquoted_csv = unquote(quoted_csv, encoding=encoding)
+
+    # Detect file encoding
+    with open(csvfile, 'rb') as csvstream:
+        sample = csvstream.read(4096)
+        encoding_detection = detect(sample)
+        if encoding_detection['confidence'] < 0.5:
+            raise Exception(f"L’encodage du fichier source n’a pas pu être déterminé")
+
+    with open(csvfile, 'r', encoding=encoding_detection['encoding']) as csvstream:
+      quoted_csv = csvstream.read()
+
+    unquoted_csv = unquote(quoted_csv, encoding=encoding_detection['encoding'])
+
     with open(csvfile, 'w') as csvstream:
         csvstream.write(unquoted_csv)
+
     source = 'onde'
     # champs obligatoires
     onde_fields = ['Nom élève', 'Prénom élève', 'Date naissance', 'Sexe', 'INE',

La demande est toujours en cours, puis-je pousser ma modification ?

#5 Mis à jour par Daniel Dehennin il y a plus de 3 ans

Cet encodage est fait par nous même dans l’EAD pour faire passer le contenu du service web au backend EAD par XMLRPC.

J’ai finalement troqué quote/unquote de urllib, qui est normalement prévu pour l’encodage d’URL, par un encodage base64.

Tout ceci se fait dans l’EAD et ne doit pas impacter les routines d’importation Scribe.

#6 Mis à jour par Joël Cuissinat il y a plus de 3 ans

  • Assigné à mis à Daniel Dehennin

#7 Mis à jour par Joël Cuissinat il y a plus de 3 ans

  • Statut changé de En cours à Résolu

#8 Mis à jour par Joël Cuissinat il y a plus de 3 ans

  • Statut changé de Résolu à Fermé
  • Restant à faire (heures) mis à 0.0

Le test Jenkins plante toujours mais plus loin :o (#30584)
J'ai testé un import de fichier par l'EAD sans erreur.

#9 Mis à jour par Joël Cuissinat il y a plus de 3 ans

  • % réalisé changé de 0 à 100
  • Temps estimé mis à 0.00 h

Formats disponibles : Atom PDF