diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 70ef0865d0af240e20aadc80871edcd607e1367b..cf9c9af9d403ae0cce7c6741fd6af0701557952c 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -97,9 +97,9 @@ fast-build:
     - ctest -V -S CI/main_ci.cmake -DIMAGE_NAME:string=ubuntu-18.04-fast
     - ccache -s
 
-contibutors-check:
+legal-check:
   extends: .common
-  only: [merge_requests, develop]
+  only: [merge_requests, develop, headers_check]
   stage: precheck
   image: $BUILD_IMAGE_REGISTRY/otb-alpine:3.7
   variables:
@@ -107,6 +107,7 @@ contibutors-check:
   allow_failure: true
   script:
     - ./CI/contributors_check.sh
+    - ./CI/headers_check.py
   after_script: []
 
 #------------------------- prepare & build jobs --------------------------------
diff --git a/CI/headers_check.py b/CI/headers_check.py
new file mode 100755
index 0000000000000000000000000000000000000000..c5011639bf5f7c14c8b2ff7e449d98023d9dcd07
--- /dev/null
+++ b/CI/headers_check.py
@@ -0,0 +1,314 @@
+#!/usr/bin/python3
+#
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2005-2019 Centre National d'Etudes Spatiales (CNES)
+#
+# This file is part of Orfeo Toolbox
+#
+#     https://www.orfeo-toolbox.org/
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import os, re
+
+# Root search directory
+topdir = '.'
+
+# Header to associate to each file extension
+# - C++ and JavaScript comments are defined by /* ... */ (=> C++ header)
+# - Shell, Python, Perl comments are defubed by # ... (=> Shell header)
+# - Text files must be processed on a case-by-case basis according to their
+#   content and what is done with them
+# - It is complicated to insert a comment in an image file and most of them
+#   were not created by the OTB project (=> no header)
+# - There is no need to insert a copyright header in the generated files or
+#   configuration files (=> no header)
+# - Most of CSS files were not created by the OTB project (=> no header)
+# - etc.
+fileext = {
+    '.cpp': 'cpp',
+    '.cxx': 'cpp',
+    '.txx': 'cpp',
+    '.h': 'cpp',
+    '.hpp': 'cpp',
+    '.hxx': 'cpp',
+    '.h.in': 'cpp',
+    '.includes': 'cpp',
+    '.i': 'cpp',
+    '.js': 'cpp',
+    '.sh': 'shell',
+    '.sh.in': 'shell',
+    '.bash': 'shell',
+    '.profile': 'shell',
+    '.py': 'shell',
+    '.py.in': 'shell',
+    '.cmake': 'shell',
+    '.cmake.in': 'shell',
+    '.yml': 'shell',
+    '.pl': 'shell',
+    '.bat': 'batch',
+    '.bat.in': 'batch',
+    '.png': 'none',
+    '.jpg': 'none',
+    '.tif': 'none',
+    '.xpm': 'none',
+    '.ico': 'none',
+    '.eps': 'none',
+    '.svg': 'none',
+    '.txt': 'none',
+    '.md': 'none',
+    '.rst': 'none',
+    '.xml': 'none',
+    '.html': 'none',
+    '.dox': 'none',
+    '.dox.in': 'none',
+    '.ts': 'none',
+    '.ui': 'none',
+    '.qrc': 'none',
+    '.rc': 'none',
+    '.rc.in': 'none',
+    '.css': 'none'
+}
+
+specialfiles = {
+    'CMakeLists.txt': 'shell',       # i.e. CMake file but Shell like comments
+    'StandaloneWrapper.in': 'shell', # i.e. CMake file but Shell like comments
+    'macx_pkgsetup.in': 'shell',
+    'linux_pkgsetup.in': 'shell'
+}
+
+# Directories to exclude from the header checking for various reasons (third
+# party works, data, patches, ...)
+excludeddirs = set([
+    './.git',
+    './Data',
+    './Modules/ThirdParty',
+    './Packaging/makeself',
+    './SuperBuild/Copyright',
+    './SuperBuild/patches'
+])
+
+# Files to exclude from the header checking for various reasons (full text of
+# licenses, binary archives, ...)
+excludedfiles = set([
+    './.clang-format',
+    './.editorconfig',
+    './.gitattributes',
+    './.gitignore',
+    './.mailmap',
+    './sonar-project.properties',
+    './LICENSE',
+    './NOTICE',
+    './VERSION',
+    './CI/ctest2junit.xsl',
+    './CI/test/README',
+    './CMake/CppcheckTargets.cmake',
+    './CMake/FindGLEW.cmake',
+    './CMake/FindKWStyle.cmake',
+    './CMake/FindLibSVM.cmake',
+    './CMake/FindOpenThreads.cmake',
+    './CMake/Findcppcheck.cmake',
+    './CMake/Findcppcheck.cpp',
+    './CMake/GenerateExportHeaderCustom.cmake',
+    './CMake/InsightValgrind-RHEL6.supp',
+    './CMake/InsightValgrind.supp',
+    './CMake/OTB_CheckCCompilerFlag.cmake',
+    './CMake/PythonCompile.py',
+    './CMake/TopologicalSort.cmake',
+    './CMake/exportheader.cmake.in',
+    './CMake/pre-commit',
+    './CMake/qt.conf.in',
+    './Documentation/Cookbook/Art/residual_registration-figure.tex',
+    './Documentation/Cookbook/rst/Makefile.in',
+    './Documentation/Cookbook/rst/conf.py.in',
+    './Modules/Visualization/Ice/README',
+    './Modules/Wrappers/SWIG/src/numpy.i',
+    './Packaging/Files/Monteverdi.icns',
+    './Packaging/Files/OTB Project.zip',
+    './Packaging/Files/qt.conf',
+    './Packaging/Files/template.app/Contents/Info.plist',
+    './Packaging/LICENSE',
+    './Packaging/howto_update_makeself',
+    './Packaging/otb_update_makeself',
+    './SuperBuild/LICENSE',
+    './Utilities/Data/Icons/LICENSE.TXT',
+    './Utilities/Data/Icons/NOTES',
+    './Utilities/Data/monteverdi.desktop.in',
+    './Utilities/Data/monteverdi2.desktop',
+    './Utilities/Doxygen/doxygen.config.in',
+    './Utilities/Maintenance/BuildHeaderTest.py',
+    './Utilities/Maintenance/fix_typos.sh'
+])
+
+regcppheader = re.compile(
+r'''/\*
+( \* Copyright \(C\) 1999-2011 Insight Software Consortium
+| \* Copyright \(C\) 20\d\d(-20\d\d)? Centre National d'Etudes Spatiales \(CNES\)
+| \* Copyright \(C\) 20\d\d(-20\d\d)? CS Systemes d'Information \(CS SI\)
+| \* Copyright \(C\) 2007-2012 Institut Mines Telecom / Telecom Bretagne
+| \* Copyright \(C\) 2005-2016 IRSTEA
+| \* Copyright \(C\) 2008 Jan Wegner
+| \* Copyright \(C\) 2007 Julien Radoux
+)+ \*
+ \* This file is part of Orfeo Toolbox
+ \*
+ \*     https://www\.orfeo-toolbox\.org/
+ \*
+ \* Licensed under the Apache License, Version 2\.0 \(the "License"\);
+ \* you may not use this file except in compliance with the License\.
+ \* You may obtain a copy of the License at
+ \*
+ \*     http://www\.apache\.org/licenses/LICENSE-2\.0
+ \*
+ \* Unless required by applicable law or agreed to in writing, software
+ \* distributed under the License is distributed on an "AS IS" BASIS,
+ \* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied\.
+ \* See the License for the specific language governing permissions and
+ \* limitations under the License\.
+ \*/
+''', flags=re.MULTILINE)
+
+regshellheader = re.compile(
+r'''(.*
+)+(# Copyright \(C\) 1999-2011 Insight Software Consortium
+|# Copyright \(C\) 20\d\d(-20\d\d)? Centre National d'Etudes Spatiales \(CNES\)
+|# Copyright \(C\) 20\d\d(-20\d\d)? CS Systemes d'Information \(CS SI\)
+|# Copyright \(C\) 2007-2012 Institut Mines Telecom / Telecom Bretagne
+)+#
+# This file is part of Orfeo Toolbox
+#
+#     https://www\.orfeo-toolbox\.org/
+#
+# Licensed under the Apache License, Version 2\.0 \(the "License"\);
+# you may not use this file except in compliance with the License\.
+# You may obtain a copy of the License at
+#
+#     http://www\.apache\.org/licenses/LICENSE-2\.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied\.
+# See the License for the specific language governing permissions and
+# limitations under the License\.
+''', flags=re.MULTILINE)
+
+regbatchheader = re.compile(
+r'''(.*
+)+(:: Copyright \(C\) 1999-2011 Insight Software Consortium
+|:: Copyright \(C\) 20\d\d(-20\d\d)? Centre National d'Etudes Spatiales \(CNES\)
+|:: Copyright \(C\) 20\d\d(-20\d\d)? CS Systemes d'Information \(CS SI\)
+|:: Copyright \(C\) 2007-2012 Institut Mines Telecom / Telecom Bretagne
+)+::
+:: This file is part of Orfeo Toolbox
+::
+::     https://www\.orfeo-toolbox\.org/
+::
+:: Licensed under the Apache License, Version 2\.0 \(the "License"\);
+:: you may not use this file except in compliance with the License\.
+:: You may obtain a copy of the License at
+::
+::     http://www\.apache\.org/licenses/LICENSE-2\.0
+::
+:: Unless required by applicable law or agreed to in writing, software
+:: distributed under the License is distributed on an "AS IS" BASIS,
+:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied\.
+:: See the License for the specific language governing permissions and
+:: limitations under the License\.
+''', flags=re.MULTILINE)
+
+
+debug = False
+returnvalue = 0
+
+
+def verifyheader(filename, category):
+
+    global returnvalue
+
+    if category == 'none':
+        if debug: print('File type ignored ({} style comment): {}'.format(category, filename))
+        return True
+
+    sourceFile = open(filename)
+    sourceContent = sourceFile.read().lstrip()
+    sourceFile.close()
+
+    if category == 'cpp':
+        m = regcppheader.match(sourceContent)
+    elif category == 'shell':
+        m = regshellheader.match(sourceContent)
+    elif category == 'batch':
+        m = regbatchheader.match(sourceContent)
+    else:
+        print('WARNING: Unable to evaluate header ({} style comment): {}'.format(category, filename))
+        returnvalue = 1
+        return False
+
+    if m:
+        if debug: print('Conform header ({} style comment): {}'.format(category, filename))
+        return True
+    else:
+        print('WARNING: Non-compliant header ({} style comment): {}'.format(category, filename))
+        returnvalue = 1
+        return False
+
+
+# Regular expression catching the file extension (with patterns '.xxx' or
+# '.xxx.yyy', like '.cmake' or '.cmake.in')
+# NOTE: '+?' operator is the not greedy / hungry version of '+' operator.
+extreg = re.compile(r'^.+?(\.[^.]+(\.[^.]+)?)$')
+
+for root, dirs, files in os.walk(topdir, topdown=True):
+
+    # "dirs[:]" modify dirs "in-place". In doing so, we can ignore some
+    # directories (here, the directories listed in the 'excludeddirs' set.
+    dirs[:] = [d for d in dirs if os.path.join(root, d) not in excludeddirs]
+
+    for f in sorted(files):
+        fullpathname = os.path.join(root, f)
+        # 1. Exclusion of files in which we do not want to search for the
+        #    copyright header.
+        if fullpathname in excludedfiles:
+            if debug: print('File excluded: {}'.format(fullpathname))
+        # 2. Early identification of files with a misleading extension
+        elif f in specialfiles.keys():
+            verifyheader(fullpathname, specialfiles[f])
+        # 3. Processing of other files according to their extension
+        else:
+            m = extreg.match(f)
+            if m:
+                # NOTE: m.group(0) = Whole string matched by the regular
+                # expression (here, the whole name of the file)
+                ext1 = m.group(1) # '.xxx' or '.xxx.yyy' form
+                ext2 = m.group(2) # '.yyy' form (optional and included in group #1)
+
+                # The test of two extensions (ext1, ext2) rather than only one
+                # make possible to distinguish known extensions, such ".cmake"
+                # or ".cmake.in", from patterns that look like a double
+                # extension, but are not, like ".remote.cmake". So, the
+                # appropriate processing can be applied (".remote.cmake" must
+                # be identified as ".cmake" and not as an unknown extension).
+                if ext1 in fileext.keys():
+                    verifyheader(fullpathname, fileext[ext1])
+                elif ext2 in fileext.keys():
+                    verifyheader(fullpathname, fileext[ext2])
+                else:
+                    print('WARNING: File with an unknown extension: {}'.format(fullpathname))
+                    returnvalue = 1
+            else:
+                print('WARNING: File without extension and not excluded: {}'.format(fullpathname))
+                returnvalue = 1
+
+exit(returnvalue)