src/pEpmodule.cc
author Volker Birk <vb@pep-project.org>
Sun, 14 Apr 2019 20:56:24 +0200
branchsync
changeset 268 81bfa5a09184
parent 265 90b64446e536
child 278 e97e3f2fd083
permissions -rw-r--r--
adding SYNC_NOTIFY_SOLE and SYNC_NOTIFY_IN_GROUP
     1 // This file is under GNU Affero General Public License 3.0
     2 // see LICENSE.txt
     3 
     4 #include "pEpmodule.hh"
     5 #include <boost/locale.hpp>
     6 #include <string>
     7 #include <sstream>
     8 #include <iomanip>
     9 #include "basic_api.hh"
    10 #include "message_api.hh"
    11 #include "user_interface.hh"
    12 #include "adapter.hh"
    13 
    14 #include <mutex>
    15 
    16 #include <pEp/key_reset.h>
    17 #include <pEp/message_api.h>
    18 #include <pEp/sync_api.h>
    19 
    20 namespace pEp {
    21     namespace PythonAdapter {
    22         using namespace std;
    23 
    24         Adapter adapter(true);
    25 
    26         void config_passive_mode(bool enable)
    27         {
    28             ::config_passive_mode(adapter.session(), enable);
    29         }
    30 
    31         void config_unencrypted_subject(bool enable)
    32         {
    33             ::config_unencrypted_subject(adapter.session(), enable);
    34         }
    35 
    36         void key_reset_user(string user_id, string fpr)
    37         {
    38             if (user_id == "")
    39                 throw invalid_argument("user_id required");
    40 
    41             PEP_STATUS status = ::key_reset_user(adapter.session(),
    42                     user_id.c_str(), fpr != "" ?  fpr.c_str() : nullptr);
    43             _throw_status(status);
    44         }
    45 
    46         void key_reset_user2(string user_id)
    47         {
    48             key_reset_user(user_id, "");
    49         }
    50 
    51         void key_reset_all_own_keys()
    52         {
    53             PEP_STATUS status = ::key_reset_all_own_keys(adapter.session());
    54             _throw_status(status);
    55         }
    56 
    57         scope *_scope = NULL;
    58 
    59         static const char *version_string = "p≡p Python adapter version 0.3";
    60         static string about()
    61         {
    62             string version = string(version_string) + "\np≡p version "
    63                 + PEP_VERSION + "\n";
    64             return version;
    65         }
    66 
    67         void _throw_status(PEP_STATUS status)
    68         {
    69             if (status == PEP_STATUS_OK)
    70                 return;
    71             if (status >= 0x400 && status <= 0x4ff)
    72                 return;
    73             if (status == PEP_OUT_OF_MEMORY)
    74                 throw bad_alloc();
    75             if (status == PEP_ILLEGAL_VALUE)
    76                 throw invalid_argument("illegal value");
    77 
    78             stringstream build;
    79             build << setfill('0') << "p≡p 0x" << setw(4) << hex << status;
    80             throw runtime_error(build.str());
    81         }
    82 
    83         PEP_STATUS _messageToSend(::message *msg)
    84         {
    85             if (!_scope)
    86                 return PEP_SEND_FUNCTION_NOT_REGISTERED;
    87 
    88             try {
    89                 object m = _scope->attr("messageToSend");
    90                 call< void >(m.ptr(), Message(msg));
    91             }
    92             catch (exception& e) { }
    93 
    94             return PEP_STATUS_OK;
    95         }
    96 
    97         void messageToSend(Message msg) {
    98             throw runtime_error("implement pEp.messageToSend(msg)");
    99         }
   100     }
   101 }
   102 
   103 BOOST_PYTHON_MODULE(pEp)
   104 {
   105     using namespace boost::python;
   106     using namespace boost::locale;
   107     using namespace pEp::PythonAdapter;
   108 
   109     docstring_options doc_options(true, false, false);
   110 
   111     generator gen;
   112     std::locale::global(gen(""));
   113     _scope = new scope();
   114 
   115     scope().attr("about") = about();
   116     
   117     def("passive_mode", pEp::PythonAdapter::config_passive_mode,
   118             "do not attach pub keys to all messages");
   119 
   120     def("unencrypted_subject", pEp::PythonAdapter::config_unencrypted_subject,
   121             "do not encrypt the subject of messages");
   122 
   123     def("key_reset", pEp::PythonAdapter::key_reset_user,
   124             "reset the default database status for the user / keypair provided\n"
   125             "This will effectively perform key_reset on each identity\n"
   126             "associated with the key and user_id, if a key is provided, and for\n"
   127             "each key (and all of their identities) if an fpr is not.");
   128 
   129     def("key_reset", pEp::PythonAdapter::key_reset_user2,
   130             "reset the default database status for the user / keypair provided\n"
   131             "This will effectively perform key_reset on each identity\n"
   132             "associated with the key and user_id, if a key is provided, and for\n"
   133             "each key (and all of their identities) if an fpr is not.");
   134 
   135     def("key_reset_all_own_keys", pEp::PythonAdapter::key_reset_all_own_keys,
   136             "revoke and mistrust all own keys, generate new keys for all\n"
   137             "own identities, and opportunistically communicate key reset\n"
   138             "information to people we have recently contacted.");
   139 
   140     auto identity_class = class_<pEp::PythonAdapter::Identity>("Identity",
   141     "Identity(address, username, user_id='', fpr='', comm_type=0, lang='en')\n"
   142     "\n"
   143     "represents a p≡p identity\n"
   144     "\n"
   145     "an identity is a network address, under which a user is represented in\n"
   146     "the network\n"
   147     "\n"
   148     "   address     network address, either an SMTP address or a URI\n"
   149     "   username    real name or nickname for user\n"
   150     "   user_id     ID this user is handled by the application\n"
   151     "   fpr         full fingerprint of the key being used as key ID,\n"
   152     "               hex encoded\n"
   153     "   comm_type   first rating level of this communication channel\n"
   154     "   lang        ISO 639-1 language code for language being preferred\n"
   155     "               on this communication channel\n"
   156         )
   157         .def(boost::python::init<string>())
   158         .def(boost::python::init<string, string>())
   159         .def(boost::python::init<string, string, string>())
   160         .def(boost::python::init<string, string, string, string>())
   161         .def(boost::python::init<string, string, string, string, int>())
   162         .def(boost::python::init<string, string, string, string, int, string>())
   163         .def("__repr__", &pEp::PythonAdapter::Identity::_repr)
   164         .def("__str__", &pEp::PythonAdapter::Identity::_str,
   165             "string representation of this identity\n"
   166             "following the pattern 'username < address >'\n"
   167                 )
   168         .def("key_reset", &pEp::PythonAdapter::Identity::key_reset,
   169                 boost::python::arg("fpr")=object(""),
   170             "reset the default database status for the identity / keypair provided. If this\n"
   171             "corresponds to the own user and a private key, also revoke the key, generate a\n"
   172             "new one, and communicate the reset to recently contacted pEp partners for this\n"
   173             "identity. If it does not, remove the key from the keyring; the key's status is\n"
   174             "completely fresh on next contact from the partner.")
   175 
   176         .add_property("address", (string(pEp::PythonAdapter::Identity::*)()) &pEp::PythonAdapter::Identity::address,
   177                 (void(pEp::PythonAdapter::Identity::*)(string)) &pEp::PythonAdapter::Identity::address,
   178                 "email address or URI")
   179         .add_property("fpr", (string(pEp::PythonAdapter::Identity::*)()) &pEp::PythonAdapter::Identity::fpr,
   180                 (void(pEp::PythonAdapter::Identity::*)(string)) &pEp::PythonAdapter::Identity::fpr,
   181                 "key ID (full fingerprint, hex encoded)")
   182         .add_property("user_id", (string(pEp::PythonAdapter::Identity::*)()) &pEp::PythonAdapter::Identity::user_id,
   183                 (void(pEp::PythonAdapter::Identity::*)(string)) &pEp::PythonAdapter::Identity::user_id,
   184                 "ID of person associated or 'pEp_own_userId' if own identity")
   185         .add_property("username", (string(pEp::PythonAdapter::Identity::*)()) &pEp::PythonAdapter::Identity::username,
   186                 (void(pEp::PythonAdapter::Identity::*)(string)) &pEp::PythonAdapter::Identity::username,
   187                 "name in full of person associated")
   188         .add_property("comm_type", (int(pEp::PythonAdapter::Identity::*)())
   189                 (PEP_comm_type(pEp::PythonAdapter::Identity::*)()) &pEp::PythonAdapter::Identity::comm_type,
   190                 (void(pEp::PythonAdapter::Identity::*)(int))
   191                 (void(pEp::PythonAdapter::Identity::*)(PEP_comm_type)) &pEp::PythonAdapter::Identity::comm_type,
   192                  "communication type, first rating level (p≡p internal)")
   193         .add_property("lang", (string(pEp::PythonAdapter::Identity::*)()) &pEp::PythonAdapter::Identity::lang,
   194                 (void(pEp::PythonAdapter::Identity::*)(string)) &pEp::PythonAdapter::Identity::lang,
   195                 "ISO 639-1 language code")
   196         .add_property("flags", (identity_flags_t(pEp::PythonAdapter::Identity::*)()) &pEp::PythonAdapter::Identity::flags,
   197                 (void(pEp::PythonAdapter::Identity::*)(identity_flags_t)) &pEp::PythonAdapter::Identity::flags,
   198                 "flags (p≡p internal)")
   199         .add_property("rating", &pEp::PythonAdapter::Identity::rating, "rating of Identity")
   200         .add_property("color", &pEp::PythonAdapter::Identity::color, "color of Identity")
   201         .def("__deepcopy__", &pEp::PythonAdapter::Identity::deepcopy)
   202         .def("update", &pEp::PythonAdapter::Identity::update, "update Identity")
   203         .def("__copy__", &pEp::PythonAdapter::Identity::copy);
   204     
   205     identity_class.attr("PEP_OWN_USERID") = "pEp_own_userId";
   206 
   207     auto blob_class = class_<Message::Blob>("Blob",
   208     "Blob(data, mime_type='', filename='')\n"
   209     "\n"
   210     "Binary large object\n"
   211     "\n"
   212     "   data            bytes-like object\n"
   213     "   mime_type       MIME type for the data\n"
   214     "   filename        filename to store the data\n" ,
   215             boost::python::init< object, char const*, char const* >(args("data", "mime_type", "filename")))
   216         .def(boost::python::init<object, string>())
   217         .def(boost::python::init<object>())
   218         .def("__repr__", &Message::Blob::_repr)
   219         .def("__len__", &Message::Blob::size, "size of Blob data in bytes")
   220         .def("decode", (string(Message::Blob::*)()) &Message::Blob::decode)
   221         .def("decode", (string(Message::Blob::*)(string)) &Message::Blob::decode,
   222     "text = blob.decode(encoding='')\n"
   223     "\n"
   224     "decode Blob data into string depending on MIME type if encoding=''\n"
   225     "\n"
   226     "   mime_type='application/pEp.sync'    decode as 'pEp.sync'\n"
   227     "   other mime_type                     decode as 'ascii' by default\n"
   228                 )
   229         .add_property("mime_type", (string(Message::Blob::*)()) &Message::Blob::mime_type,
   230                 (void(Message::Blob::*)(string)) &Message::Blob::mime_type,
   231                 "MIME type of object in Blob")
   232         .add_property("filename", (string(Message::Blob::*)()) &Message::Blob::filename,
   233                 (void(Message::Blob::*)(string)) &Message::Blob::filename,
   234                 "filename of object in Blob");
   235 
   236     ((PyTypeObject *)(void *)blob_class.ptr())->tp_as_buffer = &Message::Blob::bp;
   237 
   238     auto message_class = class_<Message>("Message",
   239     "Message(dir=1, from=None)\n"
   240     "\n"
   241     "new p≡p message\n"
   242     "\n"
   243     "   dir         1 for outgoing, 2 for incoming\n"
   244     "   from        Identity() of sender\n"
   245     "\n"
   246     "Message(mime_text)\n"
   247     "\n"
   248     "new incoming p≡p message\n"
   249     "\n"
   250     "   mime_text       text in Multipurpose Internet Mail Extensions format\n"
   251                 )
   252         .def(boost::python::init<int>())
   253         .def(boost::python::init<int, pEp::PythonAdapter::Identity *>())
   254         .def(boost::python::init<string>())
   255         .def("__str__", &Message::_str,
   256     "the string representation of a Message is it's MIME text"
   257                 )
   258         .def("__repr__", &Message::_repr)
   259         .add_property("dir", (int(Message::*)())
   260                 (PEP_msg_direction(Message::*)()) &Message::dir,
   261                 (void(Message::*)(int))
   262                 (void(Message::*)(PEP_msg_direction)) &Message::dir,
   263                 "0: incoming, 1: outgoing message")
   264         .add_property("id", (string(Message::*)()) &Message::id,
   265                 (void(Message::*)(string)) &Message::id,
   266                 "message ID")
   267         .add_property("shortmsg", (string(Message::*)()) &Message::shortmsg,
   268                 (void(Message::*)(string)) &Message::shortmsg,
   269                 "subject or short message")
   270         .add_property("longmsg", (string(Message::*)()) &Message::longmsg,
   271                 (void(Message::*)(string)) &Message::longmsg,
   272                 "body or long version of message")
   273         .add_property("longmsg_formatted", (string(Message::*)()) &Message::longmsg_formatted,
   274                 (void(Message::*)(string)) &Message::longmsg_formatted,
   275                 "HTML body or fromatted long version of message")
   276         .add_property("attachments", (boost::python::tuple(Message::*)()) &Message::attachments,
   277                 (void(Message::*)(boost::python::list)) &Message::attachments,
   278                 "tuple of Blobs with attachments; setting moves Blobs to attachment tuple")
   279         .add_property("sent", (time_t(Message::*)()) &Message::sent,
   280                 (void(Message::*)(time_t)) &Message::sent,
   281                 "time when message was sent in UTC seconds since epoch")
   282         .add_property("recv", (time_t(Message::*)()) &Message::recv,
   283                 (void(Message::*)(time_t)) &Message::recv,
   284                 "time when message was received in UTC seconds since epoch")
   285         .add_property("from_", (pEp::PythonAdapter::Identity(Message::*)()) &Message::from,
   286                 (void(Message::*)(object)) &Message::from,
   287                 "identity where message is from")
   288         .add_property("to", (boost::python::list(Message::*)()) &Message::to,
   289                 (void(Message::*)(boost::python::list)) &Message::to,
   290                 "list of identities message is going to")
   291         .add_property("recv_by", (pEp::PythonAdapter::Identity(Message::*)()) &Message::recv_by,
   292                 (void(Message::*)(object)) &Message::recv_by,
   293                 "identity where message was received by")
   294         .add_property("cc", (boost::python::list(Message::*)()) &Message::cc,
   295                 (void(Message::*)(boost::python::list)) &Message::cc,
   296                 "list of identities message is going cc")
   297         .add_property("bcc", (boost::python::list(Message::*)()) &Message::bcc,
   298                 (void(Message::*)(boost::python::list)) &Message::bcc,
   299                 "list of identities message is going bcc")
   300         .add_property("reply_to", (boost::python::list(Message::*)()) &Message::reply_to,
   301                 (void(Message::*)(boost::python::list)) &Message::reply_to,
   302                 "list of identities where message will be replied to")
   303         .add_property("in_reply_to", (boost::python::list(Message::*)()) &Message::in_reply_to,
   304                 (void(Message::*)(boost::python::list)) &Message::in_reply_to,
   305                 "in_reply_to list")
   306         .add_property("references", (boost::python::list(Message::*)()) &Message::references,
   307                 (void(Message::*)(boost::python::list)) &Message::references,
   308                 "message IDs of messages this one is referring to")
   309         .add_property("keywords", (boost::python::list(Message::*)()) &Message::keywords,
   310                 (void(Message::*)(boost::python::list)) &Message::keywords,
   311                 "keywords this message should be stored under")
   312         .add_property("comments", (string(Message::*)()) &Message::comments,
   313                 (void(Message::*)(string)) &Message::comments,
   314                 "comments added to message")
   315         .add_property("opt_fields", (dict(Message::*)()) &Message::opt_fields,
   316                 (void(Message::*)(dict)) &Message::opt_fields,
   317                 "opt_fields of message")
   318         .add_property("enc_format", (int(Message::*)())
   319                 (PEP_enc_format(Message::*)()) &Message::enc_format,
   320                 (void(Message::*)(int))
   321                 (void(Message::*)(PEP_enc_format)) &Message::enc_format,
   322                 "0: unencrypted, 1: inline PGP, 2: S/MIME, 3: PGP/MIME, 4: p≡p format")
   323         .def("encrypt", (Message(Message::*)())&Message::encrypt)
   324         .def("encrypt", (Message(Message::*)(boost::python::list))&Message::_encrypt)
   325         .def("encrypt", (Message(Message::*)(boost::python::list,int))&Message::_encrypt)
   326         .def("encrypt", (Message(Message::*)(boost::python::list,int,int))&Message::_encrypt,
   327     "msg2 = msg1.encrypt(extra_keys=[], enc_format='pEp', flags=0)\n"
   328     "\n"
   329     "encrypts a p≡p message and returns the encrypted message\n"
   330     "\n"
   331     "   extra_keys      list of strings with fingerprints for extra keys to use\n"
   332     "                   for encryption\n"
   333     "   enc_format      0 for none, 1 for partitioned, 2 for S/MIME,\n"
   334     "                   3 for PGP/MIME, 4 for pEp\n"
   335     "   flags           1 is force encryption\n"
   336                 )
   337         .def("decrypt", &Message::decrypt, boost::python::arg("flags")=0,
   338     "msg2, keys, rating, flags = msg1.decrypt()\n"
   339     "\n"
   340     "decrypts a p≡p message and returns a tuple with data\n"
   341     "\n"
   342     "   msg             the decrypted p≡p message\n"
   343     "   keys            a list of keys being used\n"
   344     "   rating          the rating of the message as integer\n"
   345     "   flags           flags set while decryption\n"
   346                 )
   347         .add_property("outgoing_rating", &Message::outgoing_rating, "rating outgoing message will have")
   348         .add_property("outgoing_color", &Message::outgoing_color, "color outgoing message will have")
   349         .def("__deepcopy__", &Message::deepcopy)
   350         .def("__copy__", &Message::copy);
   351 
   352     // basic API
   353 
   354     def("update_identity", &pEp::PythonAdapter::update_identity,
   355     "update_identity(ident)\n"
   356     "\n"
   357     "update identity information\n"
   358     "call this to complete identity information when you at least have an address\n"
   359             );
   360     def("myself", &pEp::PythonAdapter::myself,
   361     "myself(ident)\n"
   362     "\n"
   363     "ensures that the own identity is being complete\n"
   364     "supply ident.address and ident.username\n"
   365             );
   366     def("trust_personal_key", &pEp::PythonAdapter::trust_personal_key,
   367     "trust_personal_key(ident)\n"
   368     "\n"
   369     "mark a key as trusted with a person\n"
   370             );
   371 
   372     enum_<identity_flags>("identity_flags")
   373         .value("PEP_idf_not_for_sync", PEP_idf_not_for_sync)
   374         .value("PEP_idf_list", PEP_idf_list)
   375         .value("PEP_idf_devicegroup", PEP_idf_devicegroup);
   376 
   377     def("set_identity_flags", &pEp::PythonAdapter::set_identity_flags,
   378     "set_identity_flags(ident, flags)\n"
   379     "\n"
   380     "set identity flags\n"
   381             );
   382 
   383     def("unset_identity_flags", &pEp::PythonAdapter::unset_identity_flags,
   384     "unset_identity_flags(ident, flags)\n"
   385     "\n"
   386     "unset identity flags\n"
   387             );
   388 
   389     def("key_reset_trust", &pEp::PythonAdapter::key_reset_trust,
   390             "key_reset_trust(ident)\n"
   391             "\n"
   392             "reset trust bit or explicitly mistrusted status for an identity and "
   393             "its accompanying key/user_id pair\n"
   394         );
   395 
   396     // message API
   397 
   398     enum_<PEP_rating>("PEP_rating")
   399         .value("PEP_rating_undefined", PEP_rating_undefined)
   400         .value("PEP_rating_cannot_decrypt", PEP_rating_cannot_decrypt)
   401         .value("PEP_rating_have_no_key", PEP_rating_have_no_key)
   402         .value("PEP_rating_unencrypted", PEP_rating_unencrypted)
   403         .value("PEP_rating_unencrypted_for_some", PEP_rating_unencrypted_for_some)
   404         .value("PEP_rating_unreliable", PEP_rating_unreliable)
   405         .value("PEP_rating_reliable", PEP_rating_reliable)
   406         .value("PEP_rating_trusted", PEP_rating_trusted)
   407         .value("PEP_rating_trusted_and_anonymized", PEP_rating_trusted_and_anonymized)
   408         .value("PEP_rating_fully_anonymous", PEP_rating_fully_anonymous)
   409         .value("PEP_rating_mistrust", PEP_rating_mistrust)
   410         .value("PEP_rating_b0rken", PEP_rating_b0rken)
   411         .value("PEP_rating_under_attack", PEP_rating_under_attack);
   412 
   413     def("incoming_message", &incoming_message,
   414     "msg = incoming_message(mime_text)\n"
   415     "\n"
   416     "create an incoming message from a MIME text"
   417             );
   418     def("outgoing_message", &outgoing_message,
   419     "msg = outgoing_message(ident)\n"
   420     "\n"
   421     "create an outgoing message using an own identity"
   422             );
   423     def("color", &_color,
   424     "c = color(rating)\n"
   425     "\n"
   426     "calculate color value out of rating"
   427             );
   428     def("trustwords", &_trustwords,
   429     "text = trustwords(ident_own, ident_partner)\n"
   430     "\n"
   431     "calculate trustwords for two Identities");
   432 
   433     // messageToSend()
   434 
   435     def("messageToSend", &pEp::PythonAdapter::messageToSend,
   436     "messageToSend(msg)\n"
   437     "\n"
   438     "override pEp.messageToSend(msg) with your own implementation\n"
   439     "this callback is being called when a p≡p management message needs to be sent");
   440 
   441     // Sync API
   442 
   443     enum_<sync_handshake_signal>("sync_handshake_signal")
   444         .value("SYNC_NOTIFY_UNDEFINED"             , SYNC_NOTIFY_UNDEFINED)
   445         .value("SYNC_NOTIFY_INIT_ADD_OUR_DEVICE"   , SYNC_NOTIFY_INIT_ADD_OUR_DEVICE)
   446         .value("SYNC_NOTIFY_INIT_ADD_OTHER_DEVICE" , SYNC_NOTIFY_INIT_ADD_OTHER_DEVICE)
   447         .value("SYNC_NOTIFY_INIT_FORM_GROUP"       , SYNC_NOTIFY_INIT_FORM_GROUP)
   448         .value("SYNC_NOTIFY_TIMEOUT"               , SYNC_NOTIFY_TIMEOUT)
   449         .value("SYNC_NOTIFY_ACCEPTED_DEVICE_ADDED" , SYNC_NOTIFY_ACCEPTED_DEVICE_ADDED)
   450         .value("SYNC_NOTIFY_ACCEPTED_GROUP_CREATED", SYNC_NOTIFY_ACCEPTED_GROUP_CREATED)
   451         .value("SYNC_NOTIFY_OVERTAKEN"             , SYNC_NOTIFY_OVERTAKEN)
   452         .value("SYNC_NOTIFY_SOLE"                  , SYNC_NOTIFY_SOLE)
   453         .value("SYNC_NOTIFY_IN_GROUP"              , SYNC_NOTIFY_IN_GROUP);
   454 
   455     auto user_interface_class = class_<UserInterface, UserInterface_callback, boost::noncopyable>(
   456             "UserInterface",
   457     "class MyUserInterface(UserInterface):\n"
   458     "   def notifyHandshake(self, me, partner):\n"
   459     "       ...\n"
   460     "\n"
   461     "p≡p User Interface class\n"
   462     "To be used as a mixin\n"
   463     )
   464         .def("notifyHandshake", &UserInterface::notifyHandshake,
   465     "notifyHandshake(self, me, partner)\n"
   466     "\n"
   467     "   me              own identity\n"
   468     "   partner         identity of communication partner\n"
   469     "\n"
   470     "overwrite this method with an implementation of a handshake dialog")
   471         .def("deliverHandshakeResult", &UserInterface::deliverHandshakeResult,
   472                 boost::python::arg("identities")=object(),
   473     "deliverHandshakeResult(self, result, identities=None)\n"
   474     "\n"
   475     "   result          -1: cancel, 0: accepted, 1: rejected\n"
   476     "   identities      list of identities to share or None for all\n"
   477     "\n"
   478     "call to deliver the handshake result of the handshake dialog")
   479     ;
   480 
   481     // codecs
   482 
   483     call< object >(((object)(import("codecs").attr("register"))).ptr(), make_function(sync_search));
   484 }
   485