Repository: python-gitlab/python-gitlab

Issue:
Creation project with "." in name works incorrectly.
## Description of the problem, including code/CLI snippet
I'm trying to create project with "." in both name and path, using:
```python
gl.projects.create({'namespace_id': group.id, 'name': projectName, 'path': projectName})
```

## Expected Behavior
Name and path are the same and equal to actual "projectName".
URL git_url/test_group/project.test
## Actual Behavior
Name is correct, but path has "." replaced with "-".
For example "project.test" has path "project-test".
URL: git_url/test_group/project-test

## Specifications

  - python-gitlab version: 1.12.1
  - API version you are using (v3/v4): v4
  - Gitlab server version (or gitlab.com): 12.1.4-ee



Discussion:
Thanks for the report. I guess GitLab changed it now that `.` is allowed in the path and name.

I don't think that this was possible before.

Btw. it's dangerous to still run `12.1`. There are a lot of critical security vulnabilites out in the open.
Thanks for reply. We tried as an experiment disable replacement and it looks that problem was solved.
gitlab/v4/objects.py
```python
- file_path = file_path.replace('/', '%2F').replace('.', '%2E')
+ file_path = file_path.replace('/', '%2F').replace('.', '.')
```

gitlab/__init__.py
```python
- new_path = parsed.path.replace('.', '%2E')
+ new_path = parsed.path.replace('.', '.')
```

But the question is: why %2E - "." becomes %2D - "-"?

In 12.1.4 I hadn't got any problems with creating project with "." in web UI.

Resolving patch:
diff --git a/gitlab/client.py b/gitlab/client.py
--- a/gitlab/client.py
+++ b/gitlab/client.py
@@ -593,24 +593,19 @@ def http_request(
         json, data, content_type = self._prepare_send_data(files, post_data, raw)
         opts["headers"]["Content-type"] = content_type
 
-        # Requests assumes that `.` should not be encoded as %2E and will make
-        # changes to urls using this encoding. Using a prepped request we can
-        # get the desired behavior.
-        # The Requests behavior is right but it seems that web servers don't
-        # always agree with this decision (this is the case with a default
-        # gitlab installation)
-        req = requests.Request(verb, url, json=json, data=data, params=params, **opts)
-        prepped = self.session.prepare_request(req)
-        if TYPE_CHECKING:
-            assert prepped.url is not None
-        prepped.url = utils.sanitized_url(prepped.url)
-        settings = self.session.merge_environment_settings(
-            prepped.url, {}, streamed, verify, None
-        )
-
         cur_retries = 0
         while True:
-            result = self.session.send(prepped, timeout=timeout, **settings)
+            result = self.session.request(
+                method=verb,
+                url=url,
+                json=json,
+                data=data,
+                params=params,
+                timeout=timeout,
+                verify=verify,
+                stream=streamed,
+                **opts,
+            )
 
             self._check_redirects(result)
 
diff --git a/gitlab/utils.py b/gitlab/utils.py
--- a/gitlab/utils.py
+++ b/gitlab/utils.py
@@ -16,7 +16,7 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 from typing import Any, Callable, Dict, Optional
-from urllib.parse import quote, urlparse
+from urllib.parse import quote
 
 import requests
 
@@ -60,11 +60,5 @@ def clean_str_id(id: str) -> str:
     return quote(id, safe="")
 
 
-def sanitized_url(url: str) -> str:
-    parsed = urlparse(url)
-    new_path = parsed.path.replace(".", "%2E")
-    return parsed._replace(path=new_path).geturl()
-
-
 def remove_none_from_dict(data: Dict[str, Any]) -> Dict[str, Any]:
     return {k: v for k, v in data.items() if v is not None}


Test patch:
diff --git a/tests/unit/objects/test_packages.py b/tests/unit/objects/test_packages.py
--- a/tests/unit/objects/test_packages.py
+++ b/tests/unit/objects/test_packages.py
@@ -2,7 +2,6 @@
 GitLab API: https://docs.gitlab.com/ce/api/packages.html
 """
 import re
-from urllib.parse import quote_plus
 
 import pytest
 import responses
@@ -109,10 +108,9 @@
 file_name = "hello.tar.gz"
 file_content = "package content"
 package_url = "http://localhost/api/v4/projects/1/packages/generic/{}/{}/{}".format(
-    # https://datatracker.ietf.org/doc/html/rfc3986.html#section-2.3 :(
-    quote_plus(package_name).replace(".", "%2E"),
-    quote_plus(package_version).replace(".", "%2E"),
-    quote_plus(file_name).replace(".", "%2E"),
+    package_name,
+    package_version,
+    file_name,
 )
 
 
diff --git a/tests/unit/objects/test_releases.py b/tests/unit/objects/test_releases.py
--- a/tests/unit/objects/test_releases.py
+++ b/tests/unit/objects/test_releases.py
@@ -11,13 +11,12 @@
 from gitlab.v4.objects import ProjectReleaseLink
 
 tag_name = "v1.0.0"
-encoded_tag_name = "v1%2E0%2E0"
 release_name = "demo-release"
 release_description = "my-rel-desc"
 released_at = "2019-03-15T08:00:00Z"
 link_name = "hello-world"
 link_url = "https://gitlab.example.com/group/hello/-/jobs/688/artifacts/raw/bin/hello-darwin-amd64"
-direct_url = f"https://gitlab.example.com/group/hello/-/releases/{encoded_tag_name}/downloads/hello-world"
+direct_url = f"https://gitlab.example.com/group/hello/-/releases/{tag_name}/downloads/hello-world"
 new_link_type = "package"
 link_content = {
     "id": 2,
@@ -37,14 +36,12 @@
     "released_at": released_at,
 }
 
-release_url = re.compile(
-    rf"http://localhost/api/v4/projects/1/releases/{encoded_tag_name}"
-)
+release_url = re.compile(rf"http://localhost/api/v4/projects/1/releases/{tag_name}")
 links_url = re.compile(
-    rf"http://localhost/api/v4/projects/1/releases/{encoded_tag_name}/assets/links"
+    rf"http://localhost/api/v4/projects/1/releases/{tag_name}/assets/links"
 )
 link_id_url = re.compile(
-    rf"http://localhost/api/v4/projects/1/releases/{encoded_tag_name}/assets/links/1"
+    rf"http://localhost/api/v4/projects/1/releases/{tag_name}/assets/links/1"
 )
 
 
diff --git a/tests/unit/objects/test_repositories.py b/tests/unit/objects/test_repositories.py
--- a/tests/unit/objects/test_repositories.py
+++ b/tests/unit/objects/test_repositories.py
@@ -29,8 +29,7 @@ def resp_get_repository_file():
         "last_commit_id": "570e7b2abdd848b95f2f578043fc23bd6f6fd24d",
     }
 
-    # requests also encodes `.`
-    encoded_path = quote(file_path, safe="").replace(".", "%2E")
+    encoded_path = quote(file_path, safe="")
 
     with responses.RequestsMock() as rsps:
         rsps.add(
diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py
--- a/tests/unit/test_utils.py
+++ b/tests/unit/test_utils.py
@@ -30,13 +30,3 @@ def test_clean_str_id():
     src = "foo%bar/baz/"
     dest = "foo%25bar%2Fbaz%2F"
     assert dest == utils.clean_str_id(src)
-
-
-def test_sanitized_url():
-    src = "http://localhost/foo/bar"
-    dest = "http://localhost/foo/bar"
-    assert dest == utils.sanitized_url(src)
-
-    src = "http://localhost/foo.bar.baz"
-    dest = "http://localhost/foo%2Ebar%2Ebaz"
-    assert dest == utils.sanitized_url(src)