Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Encode URL user-inputted string to prevent injection #58

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/typesense/alias.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
from .utils import encodeURIComponent


class Alias(object):
def __init__(self, api_call, name):
self.api_call = api_call
self.name = name

def _endpoint_path(self):
from .aliases import Aliases
return u"{0}/{1}".format(Aliases.RESOURCE_PATH, self.name)

return "{0}/{1}".format(Aliases.RESOURCE_PATH, encodeURIComponent(self.name))

def retrieve(self):
return self.api_call.get(self._endpoint_path())
Expand Down
6 changes: 4 additions & 2 deletions src/typesense/aliases.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from typesense.alias import Alias

from .utils import encodeURIComponent


class Aliases(object):
RESOURCE_PATH = '/aliases'
RESOURCE_PATH = "/aliases"

def __init__(self, api_call):
self.api_call = api_call
Expand All @@ -15,7 +17,7 @@ def __getitem__(self, name):
return self.aliases.get(name)

def _endpoint_path(self, alias_name):
return u"{0}/{1}".format(Aliases.RESOURCE_PATH, alias_name)
return "{0}/{1}".format(Aliases.RESOURCE_PATH, encodeURIComponent(alias_name))

def upsert(self, name, mapping):
return self.api_call.put(self._endpoint_path(name), mapping)
Expand Down
8 changes: 7 additions & 1 deletion src/typesense/analytics_rule.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
from .utils import encodeURIComponent


class AnalyticsRule(object):
def __init__(self, api_call, rule_id):
self.api_call = api_call
self.rule_id = rule_id

def _endpoint_path(self):
from .analytics_rules import AnalyticsRules
return u"{0}/{1}".format(AnalyticsRules.RESOURCE_PATH, self.rule_id)

return "{0}/{1}".format(
AnalyticsRules.RESOURCE_PATH, encodeURIComponent(self.rule_id)
)

def retrieve(self):
return self.api_call.get(self._endpoint_path())
Expand Down
8 changes: 5 additions & 3 deletions src/typesense/analytics_rules.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from .analytics_rule import AnalyticsRule
from .utils import encodeURIComponent


class AnalyticsRules(object):
RESOURCE_PATH = '/analytics/rules'
RESOURCE_PATH = "/analytics/rules"

def __init__(self, api_call):
self.api_call = api_call
Expand All @@ -19,8 +20,9 @@ def create(self, rule, params=None):
return self.api_call.post(AnalyticsRules.RESOURCE_PATH, rule, params)

def upsert(self, id, rule):
return self.api_call.put(u"{0}/{1}".format(AnalyticsRules.RESOURCE_PATH, id), rule)
return self.api_call.put(
"{0}/{1}".format(AnalyticsRules.RESOURCE_PATH, encodeURIComponent(id)), rule
)

def retrieve(self):
return self.api_call.get(AnalyticsRules.RESOURCE_PATH)

8 changes: 6 additions & 2 deletions src/typesense/collection.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from .documents import Documents
from .overrides import Overrides
from .synonyms import Synonyms
from .documents import Documents
from .utils import encodeURIComponent


class Collection(object):
Expand All @@ -13,7 +14,10 @@ def __init__(self, api_call, name):

def _endpoint_path(self):
from .collections import Collections
return u"{0}/{1}".format(Collections.RESOURCE_PATH, self.name)

return "{0}/{1}".format(
Collections.RESOURCE_PATH, encodeURIComponent(self.name)
)

def retrieve(self):
return self.api_call.get(self._endpoint_path())
Expand Down
8 changes: 7 additions & 1 deletion src/typesense/conversation_model.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
from .utils import encodeURIComponent


class ConversationModel(object):
def __init__(self, api_call, model_id):
self.model_id = model_id
self.api_call = api_call

def _endpoint_path(self):
from .conversations_models import ConversationsModels
return u"{0}/{1}".format(ConversationsModels.RESOURCE_PATH, self.model_id)

return "{0}/{1}".format(
ConversationsModels.RESOURCE_PATH, encodeURIComponent(self.model_id)
)

def retrieve(self):
return self.api_call.get(self._endpoint_path())
Expand Down
14 changes: 11 additions & 3 deletions src/typesense/document.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
from .utils import encodeURIComponent


class Document(object):
def __init__(self, api_call, collection_name, document_id):
self.api_call = api_call
self.collection_name = collection_name
self.document_id = document_id

def _endpoint_path(self):
from .documents import Documents
from .collections import Collections
return u"{0}/{1}/{2}/{3}".format(Collections.RESOURCE_PATH, self.collection_name, Documents.RESOURCE_PATH,
self.document_id)
from .documents import Documents

return "{0}/{1}/{2}/{3}".format(
Collections.RESOURCE_PATH,
encodeURIComponent(self.collection_name),
Documents.RESOURCE_PATH,
encodeURIComponent(self.document_id),
)

def retrieve(self):
return self.api_call.get(self._endpoint_path())
Expand Down
62 changes: 41 additions & 21 deletions src/typesense/documents.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import json
from collections.abc import Iterable

from typesense.exceptions import TypesenseClientError

from .document import Document
from .logger import logger
from .validation import validate_search
from .preprocess import stringify_search_params
from collections.abc import Iterable
from .utils import encodeURIComponent
from .validation import validate_search


class Documents(object):
RESOURCE_PATH = 'documents'
RESOURCE_PATH = "documents"

def __init__(self, api_call, collection_name):
self.api_call = api_call
Expand All @@ -18,38 +20,44 @@ def __init__(self, api_call, collection_name):

def __getitem__(self, document_id):
if document_id not in self.documents:
self.documents[document_id] = Document(self.api_call, self.collection_name, document_id)
self.documents[document_id] = Document(
self.api_call, self.collection_name, document_id
)

return self.documents[document_id]

def _endpoint_path(self, action=None):
from .collections import Collections

action = action or ''
return u"{0}/{1}/{2}/{3}".format(Collections.RESOURCE_PATH, self.collection_name, Documents.RESOURCE_PATH,
action)
action = action or ""
return "{0}/{1}/{2}/{3}".format(
Collections.RESOURCE_PATH,
encodeURIComponent(self.collection_name),
Documents.RESOURCE_PATH,
action,
)

def create(self, document, params=None):
params = params or {}
params['action'] = 'create'
params["action"] = "create"
return self.api_call.post(self._endpoint_path(), document, params)

def create_many(self, documents, params=None):
logger.warning('`create_many` is deprecated: please use `import_`.')
logger.warning("`create_many` is deprecated: please use `import_`.")
return self.import_(documents, params)

def upsert(self, document, params=None):
params = params or {}
params['action'] = 'upsert'
params["action"] = "upsert"
return self.api_call.post(self._endpoint_path(), document, params)

def update(self, document, params=None):
params = params or {}
params['action'] = 'update'
params["action"] = "update"
return self.api_call.patch(self._endpoint_path(), document, params)

def import_jsonl(self, documents_jsonl):
logger.warning('`import_jsonl` is deprecated: please use `import_`.')
logger.warning("`import_jsonl` is deprecated: please use `import_`.")
return self.import_(documents_jsonl)

# `documents` can be either a list of document objects (or)
Expand All @@ -61,7 +69,7 @@ def import_(self, documents, params=None, batch_size=None):
batch = []
for document in documents:
batch.append(document)
if (len(batch) == batch_size):
if len(batch) == batch_size:
api_response = self.import_(batch, params)
response_objs.extend(api_response)
batch = []
Expand All @@ -75,33 +83,45 @@ def import_(self, documents, params=None, batch_size=None):
document_strs.append(json.dumps(document))

if len(document_strs) == 0:
raise TypesenseClientError(f"Cannot import an empty list of documents.")
raise TypesenseClientError(
"Cannot import an empty list of documents."
)

docs_import = '\n'.join(document_strs)
api_response = self.api_call.post(self._endpoint_path('import'), docs_import, params, as_json=False)
res_obj_strs = api_response.split('\n')
docs_import = "\n".join(document_strs)
api_response = self.api_call.post(
self._endpoint_path("import"), docs_import, params, as_json=False
)
res_obj_strs = api_response.split("\n")

response_objs = []
for res_obj_str in res_obj_strs:
try:
res_obj_json = json.loads(res_obj_str)
except json.JSONDecodeError as e:
raise TypesenseClientError(f"Invalid response - {res_obj_str}") from e
raise TypesenseClientError(
f"Invalid response - {res_obj_str}"
) from e
response_objs.append(res_obj_json)

return response_objs
else:
api_response = self.api_call.post(self._endpoint_path('import'), documents, params, as_json=False)
api_response = self.api_call.post(
self._endpoint_path("import"), documents, params, as_json=False
)
return api_response

def export(self, params=None):
api_response = self.api_call.get(self._endpoint_path('export'), params, as_json=False)
api_response = self.api_call.get(
self._endpoint_path("export"), params, as_json=False
)
return api_response

def search(self, search_parameters):
stringified_search_params = stringify_search_params(search_parameters)
validate_search(stringified_search_params)
return self.api_call.get(self._endpoint_path('search'), stringified_search_params)
return self.api_call.get(
self._endpoint_path("search"), stringified_search_params
)

def delete(self, params=None):
return self.api_call.delete(self._endpoint_path(), params)
4 changes: 3 additions & 1 deletion src/typesense/key.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from .utils import encodeURIComponent


class Key(object):
Expand All @@ -7,7 +8,8 @@ def __init__(self, api_call, key_id):

def _endpoint_path(self):
from .keys import Keys
return u"{0}/{1}".format(Keys.RESOURCE_PATH, self.key_id)

return "{0}/{1}".format(Keys.RESOURCE_PATH, encodeURIComponent(self.key_id))

def retrieve(self):
return self.api_call.get(self._endpoint_path())
Expand Down
14 changes: 11 additions & 3 deletions src/typesense/override.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
from .utils import encodeURIComponent


class Override(object):
def __init__(self, api_call, collection_name, override_id):
self.api_call = api_call
self.collection_name = collection_name
self.override_id = override_id

def _endpoint_path(self):
from .overrides import Overrides
from .collections import Collections
return u"{0}/{1}/{2}/{3}".format(Collections.RESOURCE_PATH, self.collection_name, Overrides.RESOURCE_PATH,
self.override_id)
from .overrides import Overrides

return "{0}/{1}/{2}/{3}".format(
Collections.RESOURCE_PATH,
encodeURIComponent(self.collection_name),
Overrides.RESOURCE_PATH,
encodeURIComponent(self.override_id),
)

def retrieve(self):
return self.api_call.get(self._endpoint_path())
Expand Down
18 changes: 13 additions & 5 deletions src/typesense/overrides.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from .override import Override
from .utils import encodeURIComponent


class Overrides(object):
RESOURCE_PATH = 'overrides'
RESOURCE_PATH = "overrides"

def __init__(self, api_call, collection_name):
self.api_call = api_call
Expand All @@ -11,15 +12,22 @@ def __init__(self, api_call, collection_name):

def __getitem__(self, override_id):
if override_id not in self.overrides:
self.overrides[override_id] = Override(self.api_call, self.collection_name, override_id)
self.overrides[override_id] = Override(
self.api_call, self.collection_name, override_id
)

return self.overrides[override_id]

def _endpoint_path(self, override_id=None):
from .collections import Collections
override_id = override_id or ''
return u"{0}/{1}/{2}/{3}".format(Collections.RESOURCE_PATH, self.collection_name,
Overrides.RESOURCE_PATH, override_id)

override_id = override_id or ""
return "{0}/{1}/{2}/{3}".format(
Collections.RESOURCE_PATH,
encodeURIComponent(self.collection_name),
Overrides.RESOURCE_PATH,
encodeURIComponent(override_id),
)

def upsert(self, id, schema):
return self.api_call.put(self._endpoint_path(id), schema)
Expand Down
16 changes: 12 additions & 4 deletions src/typesense/stopwords.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
from .stopwords_set import StopwordsSet
from .utils import encodeURIComponent


class Stopwords(object):
RESOURCE_PATH = '/stopwords'
RESOURCE_PATH = "/stopwords"

def __init__(self, api_call):
self.api_call = api_call
self.stopwords_sets = {}

def __getitem__(self, stopwords_set_id):
if stopwords_set_id not in self.stopwords_sets:
self.stopwords_sets[stopwords_set_id] = StopwordsSet(self.api_call, stopwords_set_id)
self.stopwords_sets[stopwords_set_id] = StopwordsSet(
self.api_call, stopwords_set_id
)

return self.stopwords_sets.get(stopwords_set_id)

def upsert(self, stopwords_set_id, stopwords_set):
return self.api_call.put('{}/{}'.format(Stopwords.RESOURCE_PATH, stopwords_set_id), stopwords_set)
return self.api_call.put(
"{}/{}".format(
Stopwords.RESOURCE_PATH, encodeURIComponent(stopwords_set_id)
),
stopwords_set,
)

def retrieve(self):
return self.api_call.get('{0}'.format(Stopwords.RESOURCE_PATH))
return self.api_call.get("{0}".format(Stopwords.RESOURCE_PATH))
Loading