@@ -173,7 +173,7 @@ def read(self, length: 'Optional[int]' = None, **kwargs: 'Any') -> 'Data_FTP':
173173
174174 def make (self ,
175175 cmmd : 'Optional[Enum_Command | str | bytes | bytearray]' = None ,
176- code : 'Optional[Enum_ReturnCode | int | str | bytes]' = None ,
176+ code : 'Optional[Enum_ReturnCode | int | str | bytes | bytearray ]' = None ,
177177 args : 'Optional[str | bytes | bytearray]' = None ,
178178 more : 'bool' = False ,
179179 charset : 'Optional[str]' = None ,
@@ -195,7 +195,10 @@ def make(self,
195195 cmmd: FTP command, a :class:`str` (an
196196 :class:`~pcapkit.const.ftp.command.Command` member is one),
197197 :class:`bytes` or :class:`bytearray`.
198- code: FTP status code.
198+ code: FTP status code, an :class:`int` in ``0``--``999`` (an
199+ :class:`~pcapkit.const.ftp.return_code.ReturnCode` member is
200+ one) or a numeric :class:`str`, :class:`bytes` or
201+ :class:`bytearray` holding one.
199202 args: Optional FTP command arguments and/or status messages, a
200203 :class:`str`, :class:`bytes` or :class:`bytearray`.
201204 more: More status messages to follow for response packets.
@@ -213,8 +216,11 @@ def make(self,
213216 ProtocolError: If neither or both of ``cmmd`` and ``code`` are
214217 given, if ``cmmd`` or ``args`` is not a :class:`str`,
215218 :class:`bytes` or :class:`bytearray` (a :obj:`bool` included),
216- or if ``lines`` are given other than as the rest of a
217- multi-line reply.
219+ if ``code`` is not an :class:`int` in ``0``--``999`` (a
220+ :obj:`bool` included) nor a numeric :class:`str`,
221+ :class:`bytes` or :class:`bytearray` holding one, or if
222+ ``lines`` are given other than as the rest of a multi-line
223+ reply.
218224
219225 """
220226 if cmmd is not None and code is None :
@@ -231,8 +237,7 @@ def make(self,
231237
232238 mf = b''
233239 elif cmmd is None and code is not None :
234- code_val = int (code )
235- prefix = b'%03d' % code_val # reply codes are three digits, so keep a leading zero
240+ prefix = self ._make_code (code )
236241
237242 mf = b'-' if more else b''
238243 else :
@@ -286,6 +291,48 @@ def _make_data(cls, data: 'Data_FTP') -> 'dict[str, Any]': # type: ignore[overr
286291 'lines' : getattr (data , 'lines' , ()),
287292 }
288293
294+ @staticmethod
295+ def _make_code (code : 'Enum_ReturnCode | int | str | bytes | bytearray' ) -> 'bytes' :
296+ """Render a reply code as the three digits a reply line opens with.
297+
298+ The three digits hold ``0``--``999``, which is what the ``[0-9]{3}``
299+ of ``FTP_RESPONSE`` reads back, so that is the range accepted rather
300+ than the 1xx--5xx of :rfc:`959#section-4.2` alone -- 6xx is
301+ :rfc:`2228#section-5`, and the read path accepts it.
302+
303+ Args:
304+ code: Reply code, an :class:`int` (a
305+ :class:`~pcapkit.const.ftp.return_code.ReturnCode` member is
306+ one) or a numeric :class:`str`, :class:`bytes` or
307+ :class:`bytearray` holding one.
308+
309+ Returns:
310+ The code as three digits, a leading zero kept.
311+
312+ Raises:
313+ ProtocolError: If ``code`` is of any other type, a :obj:`bool`
314+ included, or does not fit in three digits.
315+
316+ """
317+ # NOTE: ``ReturnCode`` is an ``IntEnum``, so a member takes the ``int``
318+ # branch. ``bool`` is an ``int`` subclass, so without the explicit check
319+ # ``True`` would write code 001, and ``int()`` alone would raise a bare
320+ # ValueError or TypeError rather than a ProtocolError (#2416).
321+ value = None # type: Optional[int]
322+ if isinstance (code , bool ):
323+ pass
324+ elif isinstance (code , int ):
325+ value = code
326+ elif isinstance (code , (str , bytes , bytearray )):
327+ try :
328+ value = int (code )
329+ except ValueError :
330+ pass
331+ if value is None or not 0 <= value <= 999 :
332+ raise ProtocolError ('FTP: invalid status code: must be an int in 0-999 or a '
333+ f'numeric str, bytes or bytearray, not { code !r} ' )
334+ return b'%03d' % value
335+
289336 @staticmethod
290337 def _test_lines (code : 'bytes' , lines : 'tuple[bytes, ...]' ) -> 'bool' :
291338 """Whether ``lines`` are the rest of a multi-line reply with ``code``.
0 commit comments