Maison  >  Article  >  développement back-end  >  Parler à un POPerver Gmail avec Python

Parler à un POPerver Gmail avec Python

PHPz
PHPzoriginal
2024-09-08 06:31:431214parcourir

Talking to a Gmail POPerver with Python

POP est un protocole relativement ancien. La première version a été spécifiée en 1984. La version encore utilisée aujourd'hui, POP3, a été spécifiée en 1996. Pour l'essayer, j'ai procédé à la connexion à un serveur Gmail POP3.

La première étape consistait à rechercher les paramètres POP3 : à quel serveur se connecter, sur quel port. Google m'a conduit ici, où j'ai trouvé les informations suivantes.

pop.gmail.com

Nécessite SSL : Oui

Port : 995

Il mentionne que SSL est requis. Ce n'était pas un problème auquel j'avais affaire il y a 25 ans, la dernière fois que je jouais avec POP. J'avais peur que ce soit un mal de tête, mais cela ne s'est avéré être aucun défi ; avec un peu d'aide de la documentation Python, je suis arrivé à ce code.

import socket
import ssl

hostname = 'pop.gmail.com'
context = ssl.create_default_context()

with socket.create_connection((hostname, 995)) as sock:
    with context.wrap_socket(sock, server_hostname=hostname) as s:
        print(s.version())

Il se connecte et me dit quelle version de SSL est utilisée... ou quelque chose du genre. Belle réussite ! Il est temps de démarrer une conversation avec le serveur.

En empruntant à la RFC officielle pour POP3, voici un exemple de conversation POP3 entre un client et un serveur/

C: <open connection>
S:    +OK POP3 server ready <1896.697170952@dbc.mtview.ca.us>
C:    USER mrose
S:    +OK mrose is a real hoopy frood
C:    PASS secret
S:    +OK mrose's maildrop has 2 messages (320 octets)
C:    STAT
S:    +OK 2 320
C:    LIST
S:    +OK 2 messages (320 octets)
S:    1 120
S:    2 200
S:    .
C:    RETR 1
S:    +OK 120 octets
S:    <the POP3 server sends message 1>
S:    .
C:    QUIT
S:    +OK dewey POP3 server signing off (maildrop empty)
C:  <close connection>

La première chose qui se produit est que le serveur envoie un message d'accueil au client. Amical. Je vais donc ajouter du code pour recevoir un message du serveur.

Lorsque vous demandez à recevoir des données d'un socket, vous devez spécifier une taille de tampon. La documentation recommande une puissance de 2, telle que 4096. De nombreuses réponses du serveur seront transmises en même temps. Certains ne le feront pas ; Parfois, un message du serveur sera divisé en plusieurs lectures et le tampon peut ne pas être rempli au maximum même s'il y a plus à venir.

Dans le cas de POP3, la façon de savoir si un message est arrivé dépend entièrement du message entrant. La plupart du temps, le serveur envoie une seule ligne de texte. (Comme nous le reverrons plus tard, ceux-ci comportent un retour chariot et des caractères de saut de ligne à la fin de chaque ligne.) Certains messages qui pourraient avoir une réponse beaucoup plus longue utilisent une autre façon de montrer qu'ils ont terminé : un point sur une seule ligne. par lui-même.

import socket
import ssl

hostname = 'pop.gmail.com'
context = ssl.create_default_context()

with socket.create_connection((hostname, 995)) as sock:
    with context.wrap_socket(sock, server_hostname=hostname) as s:
        print(s.version())
        data = s.read(4096)
        print(data)

Courez à nouveau et nous recevons un salut. Encore une belle réussite ! Notez que la ligne se termine par "rn" - caractères de retour chariot et de saut de ligne.

Vous devez transmettre une taille de tampon à la méthode read. Il disposera alors d'un tampon de cette taille disponible pour lire les données du serveur - mais il n'y a aucune garantie quant à la quantité de données qui entreront dans le tampon à la fois. Cela signifie qu'un protocole a besoin d'un moyen de spécifier quand un message est terminé. De nombreuses stratégies sont possibles. POP en utilise deux : pour tous les messages, les lignes se terminent par rn. Pour un message court (une ligne), c'est tout ce qui est requis. Pour les réponses sur plusieurs lignes, un point sur une ligne indique à lui seul que le message est complet.

TLSv1.3
b'+OK Gpop ready for requests from 2601:1c0:8301:b590:f408:d66a:3029:16ad dq2mb54750689ivb\r\n'

Maintenant, nous devons commencer à répondre au serveur. Il est temps de créer une boucle E/S (ou O/I) ; obtenez une entrée de l'utilisateur et envoyez-la au serveur. Oups ! Je ne peux pas envoyer une chaîne directement ; cela me donne une TypeError. Je dois convertir le message en octets. La méthode string encode() fera cela (l'encodage par défaut de utf-8 fonctionne très bien).

Seulement, quand je l'exécute -- oups encore ! Rien ne se passe lorsque mon message est envoyé au serveur. Parce que j'ai oublié que les messages provenant du client doivent également se terminer par rn. Un autre petit ajustement nous donne :

import socket
import ssl

hostname = 'pop.gmail.com'
context = ssl.create_default_context()

with socket.create_connection((hostname, 995)) as sock:
    with context.wrap_socket(sock, server_hostname=hostname) as s:
        print(s.version())
        while True:
            data = s.read(4096)
            print(data)
            msg = input() + "\r\n"
            s.send(msg.encode())

Super, je peux maintenant essayer de me connecter !

TLSv1.3
b'+OK Gpop ready for requests from 2601:1c0:8301:b590:f408:d66a:3029:16ad g4mb5147337iow\r\n'
USER grokprogramming
b'+OK send PASS\r\n'
PASS trustno1
b'-ERR [AUTH] Application-specific password required: https://support.google.com/accounts/answer/185833\r\n'

OK, donc suivre ce lien m'amène à une page où je peux configurer un mot de passe spécifique à l'application. Une pierre d'achoppement potentielle que j'ai rencontrée : votre compte doit avoir une authentification à deux facteurs activée pour que vous puissiez créer un mot de passe spécifique à une application, pour autant que je sache. Pourquoi n'aurais-je pas activé l'authentification à deux facteurs l'année de notre Seigneur 2024 ? Je ne peux pas le dire. Je le fais maintenant.

Muni d'un mot de passe spécifique à l'application (attention à bien retirer les espaces), je peux me connecter ! Ensuite, j'émettrai la commande STAT qui me dira combien de messages j'ai et leur taille combinée. Après cela, j'émettrai la commande LIST, qui renverra une liste de messages avec un identifiant et une taille pour chacun.

TLSv1.3
b'+OK Gpop ready for requests from 2601:1c0:8301:b590:f408:d66a:3029:16ad e18mb76868856iow\r\n'
USER grokprogramming
b'+OK send PASS\r\n'
PASS baygdsgkmihkckrb
b'+OK Welcome.\r\n'
STAT
b'+OK 263 14191565\r\n'
LIST
b'+OK 263 messages (14191565 bytes)\r\n1 2778\r\n2 2947\r\n3 6558\r\n4 9864\r\n5 35997\r\n6 45462\r\n7 45462\r\n8 63894\r\n9 11487\r\n10 74936\r\n11 74925\r\n12 11632\r\n13 32392\r\n14 74997\r\n15 51961\r\n16 15375\r\n17 46513\r\n18 21519\r\n19 15966\r\n20 27258\r\n21 28503\r\n22 35615\r\n23 86353\r\n24 280'

J'ai rencontré un bug dans le code. La réponse pour LIST s'étend sur plusieurs lignes et, dans ce cas, nécessitera plusieurs lectures de tampon. L'ensemble du message se terminera par un point sur une ligne distincte. Ici, j'ai reçu un tampon du message, et maintenant je dois appuyer sur Entrée et envoyer un message vide au serveur pour que le code passe à l'itération suivante de la boucle et soit lu à nouveau dans le tampon.

Je vais modifier le code pour que l'utilisateur ait toujours la possibilité de relire ou non le tampon. Je vais aussi enfin décoder les octets entrants du serveur, pour que le texte soit plus joli.

import socket
import ssl

hostname = 'pop.gmail.com'
context = ssl.create_default_context()

with socket.create_connection((hostname, 995)) as sock:
    with context.wrap_socket(sock, server_hostname=hostname) as s:
        print(s.version())
        while True:
            data = s.read(4096)
            print(data.decode())
            while input("more? y/[n]: ") == "y":
                data = s.read(4096)
                print(data.decode())
            msg = input("> ") + "\r\n"
            s.send(msg.encode())

Et voici une session complète comprenant la récupération d'un email et l'envoi du message de déconnexion.

> USER grokprogramming
+OK send PASS

more? y/[n]: 
> PASS trustno1
+OK Welcome.

more? y/[n]: 
> STAT
+OK 263 14191565

more? y/[n]: 
> LIST
+OK 263 messages (14191565 bytes)
1 2778
2 2947
3 6558
<...>
260 41300
261 114059
262 174321
263 39206
.

more? y/[n]: 
> RETR 1
+OK message follows
MIME-Version: 1.0
Received: by 10.76.81.230; Thu, 28 Jun 2012 20:21:50 -0700 (PDT)
Date: Thu, 28 Jun 2012 20:21:50 -0700
Message-ID: <CADBp03TWFOKcTOaK_0P7VV2GB+TZsoSd_W4G5nZKKs7pdk6cWQ@mail.gmail.com>
Subject: Customize Gmail with colors and themes
From: Gmail Team <mail-noreply@google.com>
To: Grok Programming <grokprogramming@gmail.com>
Content-Type: multipart/alternative; boundary=e0cb4e385592f8025004c393f2b4

--e0cb4e385592f8025004c393f2b4
Content-Type: text/plain; charset=ISO-8859-1
Content-Transfer-Encoding: quoted-printable

To spice up your inbox with colors and themes, check out the Themes tab
under Settings.
       Customize Gmail =BB <https://mail.google.com/mail/#settings/themes>

Enjoy!

- The Gmail Team
[image: Themes thumbnails]

Please note that Themes are not available if you're using Internet Explorer
6.0. To take advantage of the latest Gmail features, please upgrade to a
fully supported
browser<http://support.google.com/mail/bin/answer.py?answer=3D6557&hl=3Den&=
utm_source=3Dwel-eml&utm_medium=3Deml&utm_campaign=3Den>
..

--e0cb4e385592f8025004c393f2b4
Content-Type: text/html; charset=ISO-8859-1

more? y/[n]: y

<html>
<font face="Arial, Helvetica, sans-serif">
<p>To spice up your inbox with colors and themes, check out the Themes tab
under Settings.</p>

<table cellpadding="0" cellspacing="0">
  <col style="width: 1px;"/>
  <col/>
  <col style="width: 1px;"/>
  <tr>
    <td></td>
    <td height="1px" style="background-color: #ddd"></td>
    <td></td>
  </tr>
  <tr>
    <td style="background-color: #ddd"></td>
    <td background="https://mail.google.com/mail/images/welcome-button-background.png"
        style="background-color: #ddd; background-repeat: repeat-x;
            padding: 10px; font-size: larger">
          <a href="https://mail.google.com/mail/#settings/themes"
            style="font-weight: bold; color: #000; text-decoration: none;
            display: block;">
      Customize Gmail &#187;</a>
    </td>
    <td style="ba
more? y/[n]: y
ckground-color: #ddd"></td>
  </tr>
 <tr>
    <td></td>
    <td height="1px" style="background-color: #ddd"></td>
    <td></td>
  </tr>
</table>

<p>Enjoy!</p>

<p>- The Gmail Team</p>

<img width="398" height="256" src="https://mail.google.com/mail/images/gmail_themes_2.png" alt="Themes thumbnails" />

<p><font size="-2" color="#999">Please note that Themes are not available if
you're using Internet Explorer 6.0. To take advantage of the latest Gmail
features, please
<a href="http://support.google.com/mail/bin/answer.py?answer=6557&hl=en&utm_source=wel-eml&utm_medium=eml&utm_campaign=en"><font color="#999">
upgrade to a fully supported browser</font></a>.</font></p>

</font>
</html>

--e0cb4e385592f8025004c393f2b4--
.

more? y/[n]: 
> QUIT
+OK Farewell.

more? y/[n]: 
> 

Yet another great success! I was able to log in to the POP3 server and retrieve a message. The script in its current state is pretty flexible, but it requires a lot of work from the user. I'll make a few final tweaks to make interacting with the POP3 server a little easier: if the user starts a message to the server with a "!" it will be stripped out, but the script will read in data from the server until it gets to a period on a line by itself -- in other words, for commands with long responses. No "!" and the script will read in a single line, looking for the \r\n characters.

import socket
import ssl

hostname = 'pop.gmail.com'
context = ssl.create_default_context()

def read_until(s, eom):
    # read into the buffer at least once
    data = s.read(4096)
    # continue reading until end of message
    while data[-len(eom):] != eom:
        data += s.read(4096)
    # return incoming bytes decoded to a string
    return data.decode()

def read_single_line(s):
    return read_until(s, b"\r\n")

def read_muli_line(s):
    return read_until(s, b"\r\n.\r\n")

with socket.create_connection((hostname, 995)) as sock:
    with context.wrap_socket(sock, server_hostname=hostname) as s:
        print(s.version())
        print(read_single_line(s))
        msg = input("> ")
        # empty msg will close connection
        while msg != "":
            if msg[0] == "!":
                msg = msg[1:]
                long = True
            else:
                long = False
            msg += "\r\n"
            s.send(msg.encode())
            if long:
                print(read_muli_line(s))
            else:
                print(read_single_line(s))
            msg = input("> ")
        s.close()

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn