예제 #1
1
func mediaTypeOrDefault(header textproto.MIMEHeader) string {
	mediaType, _, err := mime.ParseMediaType(header.Get("Content-Type"))
	if err != nil {
		return "application/octet-stream"
	}
	return mediaType
}
예제 #2
1
func checkContentType(h textproto.MIMEHeader, expected string) error {
	ctype := h.Get("Content-Type")
	if ctype != expected {
		return errors.Errorf("expected Content-Type %q, got %q", expected, ctype)
	}
	return nil
}
예제 #3
0
파일: address.go 프로젝트: sunfmin/mimemail
// Date parses the Date header field.
func Date(h textproto.MIMEHeader) (time.Time, error) {
	hdr := h.Get("Date")
	if hdr == "" {
		return time.Time{}, ErrHeaderNotPresent
	}
	return parseDate(hdr)
}
예제 #4
0
파일: entity.go 프로젝트: k3nju/emailtk
func newEntity(h textproto.MIMEHeader, r io.Reader, shared *sharedData) (*Entity, error) {
	contType := h.Get("Content-Type")
	if contType == "" {
		return &Entity{
			Header: h,
			body:   &SinglepartBody{Reader: r},
			shared: shared,
		}, nil
	}

	mediaType, params, err := mime.ParseMediaType(contType)
	if err != nil {
		return nil, err
	}

	if !strings.HasPrefix(mediaType, "multipart/") {
		return &Entity{
			Header: h,
			body:   &SinglepartBody{Reader: r},
			shared: shared,
		}, nil
	}

	boundary, ok := params["boundary"]
	if !ok {
		return nil, fmt.Errorf("Boundary not found in Content-Type field: %v", contType)
	}

	return &Entity{
		Header: h,
		body:   &MultipartBody{multipart.NewReader(r, boundary)},
		shared: shared,
	}, nil
}
예제 #5
0
//handleEventMsg processes event messages received from Freeswitch.
func (client *Client) handleEventMsg(resp textproto.MIMEHeader) error {
	event := make(map[string]string)
	//Check that Content-Length is numeric.
	_, err := strconv.Atoi(resp.Get("Content-Length"))
	if err != nil {
		log.Print(logPrefix, "Invalid Content-Length", err)
		return err
	}

	for {
		//Read each line of the event and store into map.
		line, err := client.eventConn.ReadLine()
		if err != nil {
			log.Print(logPrefix, "Event Read failure: ", err)
			return err
		}

		if line == "" { //Empty line means end of event.
			client.sendEvent(event)
			return err
		}

		parts := strings.Split(line, ": ") //Split "Key: value"
		key := parts[0]
		value, err := url.QueryUnescape(parts[1])

		if err != nil {
			log.Print(logPrefix, "Parse failure: ", err)
			return err
		}

		event[key] = value
	}
}
예제 #6
0
파일: email.go 프로젝트: louisyoo/email
// parseMIMEParts will recursively walk a MIME entity and return a []mime.Part containing
// each (flattened) mime.Part found.
// It is important to note that there are no limits to the number of recursions, so be
// careful when parsing unknown MIME structures!
func parseMIMEParts(hs textproto.MIMEHeader, b io.Reader) ([]*part, error) {
	var ps []*part
	// If no content type is given, set it to the default
	if _, ok := hs["Content-Type"]; !ok {
		hs.Set("Content-Type", defaultContentType)
	}
	ct, params, err := mime.ParseMediaType(hs.Get("Content-Type"))
	if err != nil {
		return ps, err
	}
	// If it's a multipart email, recursively parse the parts
	if strings.HasPrefix(ct, "multipart/") {
		if _, ok := params["boundary"]; !ok {
			return ps, ErrMissingBoundary
		}
		mr := multipart.NewReader(b, params["boundary"])
		for {
			var buf bytes.Buffer
			p, err := mr.NextPart()
			if err == io.EOF {
				break
			}
			if err != nil {
				return ps, err
			}
			if _, ok := p.Header["Content-Type"]; !ok {
				p.Header.Set("Content-Type", defaultContentType)
			}
			subct, _, err := mime.ParseMediaType(p.Header.Get("Content-Type"))
			if strings.HasPrefix(subct, "multipart/") {
				sps, err := parseMIMEParts(p.Header, p)
				if err != nil {
					return ps, err
				}
				ps = append(ps, sps...)
			} else {
				// Otherwise, just append the part to the list
				// Copy the part data into the buffer
				if _, err := io.Copy(&buf, p); err != nil {
					return ps, err
				}
				ps = append(ps, &part{body: buf.Bytes(), header: p.Header})
			}
		}
	} else {
		// If it is not a multipart email, parse the body content as a single "part"
		var buf bytes.Buffer
		if _, err := io.Copy(&buf, b); err != nil {
			return ps, err
		}
		ps = append(ps, &part{body: buf.Bytes(), header: hs})
	}
	return ps, nil
}
예제 #7
0
//handleAPIMsg processes API response messages received from Freeswitch.
func (client *Client) handleAPIMsg(resp textproto.MIMEHeader) error {
	//Check that Content-Length is numeric.
	length, err := strconv.Atoi(resp.Get("Content-Length"))
	if err != nil {
		log.Print(logPrefix, "Invalid Content-Length", err)
		client.sendCmdRes(cmdRes{body: "", err: err}, true)
		return err
	}

	//Read Content-Length bytes into a buffer and convert to string.
	buf := make([]byte, length)
	if _, err = io.ReadFull(client.eventConn.R, buf); err != nil {
		log.Print(logPrefix, "API Read failure: ", err)
	}
	client.sendCmdRes(cmdRes{body: string(buf), err: err}, true)
	return err
}
예제 #8
0
func TestSubjectHeaderWithExistingQuotes(t *testing.T) {
	m := simpleMessage()
	m.Subject = `"Hi World"`
	buf := new(bytes.Buffer)
	header := textproto.MIMEHeader{}

	_, err := m.bytes(buf, header)
	if err != nil {
		t.Log(err)
		t.Fail()
	}

	expected := `\"Hi World\"`
	if sub := header.Get("Subject"); sub != expected {
		t.Logf(`Expected Subject to be "%s" but got "%s"`, expected, sub)
		t.Fail()
	}
}
예제 #9
0
파일: nntp.go 프로젝트: ZiRo-/srndv2
// store message, unpack attachments, register with daemon, send to daemon for federation
// in that order
func (self *nntpConnection) storeMessage(daemon *NNTPDaemon, hdr textproto.MIMEHeader, body io.Reader) (err error) {
	var f io.WriteCloser
	msgid := getMessageID(hdr)
	if msgid == "" {
		// drop, invalid header
		log.Println(self.name, "dropping message with invalid mime header, no message-id")
		_, err = io.Copy(Discard, body)
		return
	} else if ValidMessageID(msgid) {
		f = daemon.store.CreateFile(msgid)
	} else {
		// invalid message-id
		log.Println(self.name, "dropping message with invalid message-id", msgid)
		_, err = io.Copy(Discard, body)
		return
	}
	if f == nil {
		// could not open file, probably already storing it from another connection
		log.Println(self.name, "discarding duplicate message")
		_, err = io.Copy(Discard, body)
		return
	}
	path := hdr.Get("Path")
	hdr.Set("Path", daemon.instance_name+"!"+path)
	// now store attachments and article
	err = writeMIMEHeader(f, hdr)
	if err == nil {
		err = daemon.store.ProcessMessageBody(f, hdr, body)
		if err == nil {
			// tell daemon
			daemon.loadFromInfeed(msgid)
		}
	}
	f.Close()
	if err != nil {
		// clean up
		if ValidMessageID(msgid) {
			DelFile(daemon.store.GetFilename(msgid))
		}
	}
	return
}
예제 #10
0
파일: util.go 프로젝트: ZiRo-/srndv2
// get a message id from a mime header
// checks many values
func getMessageID(hdr textproto.MIMEHeader) (msgid string) {
	msgid = hdr.Get("Message-Id")
	if msgid == "" {
		msgid = hdr.Get("Message-ID")
	}
	if msgid == "" {
		msgid = hdr.Get("message-id")
	}
	if msgid == "" {
		msgid = hdr.Get("MESSAGE-ID")
	}
	return
}
예제 #11
0
// Read body from text/plain
func readPlainText(header textproto.MIMEHeader, body io.Reader) (mailbody []byte, err error) {
	contentType := header.Get("Content-Type")
	encoding := header.Get("Content-Transfer-Encoding")
	_, params, err := mime.ParseMediaType(contentType)
	if encoding == ENC_QUOTED_PRINTABLE {
		if strings.ToLower(params["charset"]) == CHARSET_ISO2022JP {
			mailbody, err = ioutil.ReadAll(transform.NewReader(quotedprintable.NewReader(body), japanese.ISO2022JP.NewDecoder()))
		} else {
			mailbody, err = ioutil.ReadAll(quotedprintable.NewReader(body))
		}
	} else if encoding == ENC_BASE64 {
		mailbody, err = ioutil.ReadAll(base64.NewDecoder(base64.StdEncoding, body))
	} else if len(contentType) == 0 || strings.ToLower(params["charset"]) == CHARSET_ISO2022JP {
		// charset=ISO-2022-JP
		mailbody, err = ioutil.ReadAll(transform.NewReader(body, japanese.ISO2022JP.NewDecoder()))
	} else {
		// encoding = 8bit or 7bit
		mailbody, err = ioutil.ReadAll(body)
	}
	return mailbody, err
}
예제 #12
0
파일: email.go 프로젝트: eswdd/bosun
// parseMIMEParts will recursively walk a MIME entity and return a []mime.Part containing
// each (flattened) mime.Part found.
// It is important to note that there are no limits to the number of recursions, so be
// careful when parsing unknown MIME structures!
func parseMIMEParts(hs textproto.MIMEHeader, b io.Reader) ([]*part, error) {
	var ps []*part
	ct, params, err := mime.ParseMediaType(hs.Get("Content-Type"))
	if err != nil {
		return ps, err
	}
	if strings.HasPrefix(ct, "multipart/") {
		if _, ok := params["boundary"]; !ok {
			return ps, ErrMissingBoundary
		}
		mr := multipart.NewReader(b, params["boundary"])
		for {
			var buf bytes.Buffer
			p, err := mr.NextPart()
			if err == io.EOF {
				break
			}
			if err != nil {
				return ps, err
			}
			subct, _, err := mime.ParseMediaType(p.Header.Get("Content-Type"))
			if strings.HasPrefix(subct, "multipart/") {
				sps, err := parseMIMEParts(p.Header, p)
				if err != nil {
					return ps, err
				}
				ps = append(ps, sps...)
			} else {
				// Otherwise, just append the part to the list
				// Copy the part data into the buffer
				if _, err := io.Copy(&buf, p); err != nil {
					return ps, err
				}
				ps = append(ps, &part{body: buf.Bytes(), header: p.Header})
			}
		}
	}
	return ps, nil
}
예제 #13
0
파일: address.go 프로젝트: sunfmin/mimemail
// AddressList parses the named header field as a list of addresses.
func AddressList(header textproto.MIMEHeader, key string, utf8ReaderFactory UTF8ReaderFactory) (r []*Address, err error) {
	// h := make(map[string][]string)

	// vals := header[key]
	// if vals == nil {
	// 	return
	// }
	// var newDecodedVals []string
	// for _, val := range vals {
	// 	var newVal string
	// 	newVal, err = DecodeText(val, utf8ReaderFactory)
	// 	if err != nil {
	// 		return
	// 	}
	// 	newDecodedVals = append(newDecodedVals, newVal)
	// }
	// h[key] = newDecodedVals
	hdr := header.Get(key)
	if hdr == "" {
		return nil, ErrHeaderNotPresent
	}

	return newAddrParser(hdr, utf8ReaderFactory).parseAddressList()
}
예제 #14
0
파일: gami.go 프로젝트: Darigaaz/gami
//newEvent build event
func newEvent(data *textproto.MIMEHeader) (*AMIEvent, error) {
	if data.Get("Event") == "" {
		return nil, errNotEvent
	}
	ev := &AMIEvent{data.Get("Event"), strings.Split(data.Get("Privilege"), ","), make(map[string]string)}
	for k, v := range *data {
		if k == "Event" || k == "Privilege" {
			continue
		}
		ev.Params[k] = v[0]
	}
	return ev, nil
}
예제 #15
0
파일: gami.go 프로젝트: Darigaaz/gami
//newResponse build a response for action
func newResponse(data *textproto.MIMEHeader) (*AMIResponse, error) {
	if data.Get("Response") == "" {
		return nil, errors.New("Not Response")
	}
	response := &AMIResponse{"", "", make(map[string]string)}
	for k, v := range *data {
		if k == "Response" {
			continue
		}
		response.Params[k] = v[0]
	}
	response.ID = data.Get("Actionid")
	response.Status = data.Get("Response")
	return response, nil
}
예제 #16
0
파일: mail.go 프로젝트: kaey/mail
// decodeBody parses body of a message, filling m.Body, m.HTML and m.Parts.
func (m *Message) decodeBody(r io.Reader, h textproto.MIMEHeader) error {
	cth := h.Get("Content-Type")
	if cth == "" {
		cth = "text/plain"
	}
	ct, ctp, err := mime.ParseMediaType(cth)
	if err != nil {
		return fmt.Errorf("invalid content-type: %q", cth)
	}

	// Find name.
	filename := ctp["name"]
	if filename == "" {
		cdh := h.Get("Content-Disposition")
		if cdh != "" {
			_, cdp, err := mime.ParseMediaType(cdh)
			if err != nil {
				return fmt.Errorf("invalid content-disposition: %q", cdh)
			}
			filename = cdp["filename"]

		}
	}

	// If it has filename, add as attachment.
	if filename != "" {
		name, err := decodeHeader(filename)
		if err != nil {
			return fmt.Errorf("decode filename: %v", err)
		}
		data, err := ioutil.ReadAll(decodeTransfer(r, h.Get("Content-Transfer-Encoding")))
		if err != nil {
			return fmt.Errorf("read attachment: %v", err)
		}

		m.Parts = append(m.Parts, Part{Name: name, Data: data})
		return nil
	}

	if ct == "text/plain" || ct == "text/html" {
		buf := new(bytes.Buffer)
		for {
			data, err := ioutil.ReadAll(decodeTransfer(r, h.Get("Content-Transfer-Encoding")))
			buf.Write(data)
			if err != nil {
				if _, ok := err.(base64.CorruptInputError); ok {
					continue
				}
				return fmt.Errorf("read body: %v", err)
			}
			break
		}

		body, err := decodeCharset(buf.String(), ctp["charset"])
		if err != nil {
			return fmt.Errorf("charsetDecode: %v", err)
		}

		if ct == "text/html" {
			m.HTML += body
			return nil
		}

		m.Body += body
		return nil
	}

	if strings.HasPrefix(ct, "multipart/") {
		r := multipart.NewReader(r, ctp["boundary"])
		for {
			p, err := r.NextPart()
			if err != nil {
				if err == io.EOF {
					break
				}
				return fmt.Errorf("next part: %q", err)
			}

			if err := m.decodeBody(p, p.Header); err != nil {
				p.Close() // p.Close is also called automatically by r.NextPart.
				return err
			}
		}
		return nil
	}

	// TODO: decide what to do with this.
	//return fmt.Errorf("content-type without filename: %q", ct)
	return nil
}
예제 #17
0
파일: object.go 프로젝트: jpfielding/gorets
// NewObjectFromStream ...
func NewObjectFromStream(header textproto.MIMEHeader, body io.ReadCloser) (*Object, error) {
	objectID, err := strconv.ParseInt(header.Get("Object-ID"), 10, 64)
	if err != nil {
		// Attempt to parse a Rets Response code (if it exists)
		resp, parseErr := ReadResponse(body)
		if parseErr != nil {
			return nil, err
		}
		// Include a GetObject (empty of content) so that its rets response can be retrieved
		emptyResult := Object{
			RetsMessage: resp,
			RetsError:   resp.Code != StatusOK,
		}
		return &emptyResult, err
	}
	preferred, err := strconv.ParseBool(header.Get("Preferred"))
	if err != nil {
		preferred = false
	}
	objectData := make(map[string]string)
	for _, v := range header[textproto.CanonicalMIMEHeaderKey("ObjectData")] {
		kv := strings.Split(v, "=")
		objectData[kv[0]] = kv[1]
	}
	blob, err := ioutil.ReadAll(body)
	if err != nil {
		return nil, err
	}

	// 5.6.7
	retsError, err := strconv.ParseBool(header.Get("RETS-Error"))
	retsMsg, err := ReadResponse(ioutil.NopCloser(bytes.NewReader(blob)))

	// there is a rets message, stash it and wipe the content
	if err == nil {
		blob = nil
	}

	object := Object{
		// required
		ObjectID:    int(objectID),
		ContentID:   header.Get("Content-ID"),
		ContentType: header.Get("Content-Type"),
		// optional
		UID:            header.Get("UID"),
		Description:    header.Get("Content-Description"),
		SubDescription: header.Get("Content-Sub-Description"),
		Location:       header.Get("Location"),
		RetsError:      retsError,
		RetsMessage:    retsMsg,
		Preferred:      preferred,
		ObjectData:     objectData,
		Blob:           blob,
	}

	return &object, nil
}
예제 #18
0
파일: nntp.go 프로젝트: kurtcoke/srndv2
func (self nntpConnection) handleLine(daemon NNTPDaemon, code int, line string, conn *textproto.Conn) (err error) {
	parts := strings.Split(line, " ")
	var msgid string
	if code == 0 && len(parts) > 1 {
		msgid = parts[1]
	} else {
		msgid = parts[0]
	}
	if code == 238 {
		if ValidMessageID(msgid) {
			log.Println("sending", msgid, "to", self.name)
			// send the article to us
			self.take <- msgid
		}
	} else if code == 239 {
		// successful TAKETHIS
		log.Println(msgid, "sent via", self.name)
		// TODO: remember success
	} else if code == 431 {
		// CHECK said we would like this article later
		log.Println("defer sending", msgid, "to", self.name)
		go self.articleDefer(msgid)
	} else if code == 439 {
		// TAKETHIS failed
		log.Println(msgid, "was not sent to", self.name, "denied:", line)
		// TODO: remember denial
	} else if code == 438 {
		// they don't want the article
		// TODO: remeber rejection
	} else {
		// handle command
		parts := strings.Split(line, " ")
		if len(parts) == 2 {
			cmd := parts[0]
			if cmd == "MODE" {
				if parts[1] == "READER" {
					// reader mode
					self.mode = "READER"
					log.Println(self.name, "switched to reader mode")
					conn.PrintfLine("201 No posting Permitted")
				} else if parts[1] == "STREAM" {
					// wut? we're already in streaming mode
					log.Println(self.name, "already in streaming mode")
					conn.PrintfLine("203 Streaming enabled brah")
				} else {
					// invalid
					log.Println(self.name, "got invalid mode request", parts[1])
					conn.PrintfLine("501 invalid mode variant:", parts[1])
				}
			} else if cmd == "QUIT" {
				// quit command
				conn.PrintfLine("")
				// close our connection and return
				conn.Close()
				return
			} else if cmd == "CHECK" {
				// handle check command
				msgid := parts[1]
				// have we seen this article?
				if daemon.database.HasArticle(msgid) {
					// yeh don't want it
					conn.PrintfLine("438 %s", msgid)
				} else if daemon.database.ArticleBanned(msgid) {
					// it's banned we don't want it
					conn.PrintfLine("438 %s", msgid)
				} else {
					// yes we do want it and we don't have it
					conn.PrintfLine("238 %s", msgid)
				}
			} else if cmd == "TAKETHIS" {
				// handle takethis command
				var hdr textproto.MIMEHeader
				var reason string
				// read the article header
				hdr, err = conn.ReadMIMEHeader()
				if err == nil {
					// check the header
					reason, err = self.checkMIMEHeader(daemon, hdr)
					dr := conn.DotReader()
					if len(reason) > 0 {
						// discard, we do not want
						code = 439
						log.Println(self.name, "rejected", msgid, reason)
						_, err = io.Copy(ioutil.Discard, dr)
						err = daemon.database.BanArticle(msgid, reason)
					} else {
						// check if we don't have the rootpost
						reference := hdr.Get("References")
						newsgroup := hdr.Get("Newsgroups")
						if reference != "" && ValidMessageID(reference) && !daemon.store.HasArticle(reference) && !daemon.database.IsExpired(reference) {
							log.Println(self.name, "got reply to", reference, "but we don't have it")
							daemon.ask_for_article <- ArticleEntry{reference, newsgroup}
						}
						f := daemon.store.CreateTempFile(msgid)
						if f == nil {
							log.Println(self.name, "discarding", msgid, "we are already loading it")
							// discard
							io.Copy(ioutil.Discard, dr)
						} else {
							// write header
							err = writeMIMEHeader(f, hdr)
							// write body
							_, err = io.Copy(f, dr)
							if err == nil || err == io.EOF {
								f.Close()
								// we gud, tell daemon
								daemon.infeed_load <- msgid
							} else {
								log.Println(self.name, "error reading message", err)
							}
						}
						code = 239
						reason = "gotten"
					}
				} else {
					log.Println(self.name, "error reading mime header:", err)
					code = 439
					reason = "error reading mime header"
				}
				conn.PrintfLine("%d %s %s", code, msgid, reason)
			} else if cmd == "ARTICLE" {
				if ValidMessageID(msgid) {
					if daemon.store.HasArticle(msgid) {
						// we have it yeh
						f, err := os.Open(daemon.store.GetFilename(msgid))
						if err == nil {
							conn.PrintfLine("220 %s", msgid)
							dw := conn.DotWriter()
							_, err = io.Copy(dw, f)
							dw.Close()
							f.Close()
						} else {
							// wtf?!
							conn.PrintfLine("503 idkwtf happened: %s", err.Error())
						}
					} else {
						// we dont got it
						conn.PrintfLine("430 %s", msgid)
					}
				} else {
					// invalid id
					conn.PrintfLine("500 Syntax error")
				}
			}
		}
	}
	return
}
예제 #19
0
파일: nntp.go 프로젝트: kurtcoke/srndv2
// check if we want the article given its mime header
// returns empty string if it's okay otherwise an error message
func (self nntpConnection) checkMIMEHeader(daemon NNTPDaemon, hdr textproto.MIMEHeader) (reason string, err error) {

	newsgroup := hdr.Get("Newsgroups")
	reference := hdr.Get("References")
	msgid := hdr.Get("Message-Id")
	encaddr := hdr.Get("X-Encrypted-Ip")
	torposter := hdr.Get("X-Tor-Poster")
	i2paddr := hdr.Get("X-I2p-Desthash")
	content_type := hdr.Get("Content-Type")
	has_attachment := strings.HasPrefix(content_type, "multipart/mixed")
	pubkey := hdr.Get("X-Pubkey-Ed25519")
	// TODO: allow certain pubkeys?
	is_signed := pubkey != ""
	is_ctl := newsgroup == "ctl" && is_signed
	anon_poster := torposter != "" || i2paddr != "" || encaddr == ""

	if !newsgroupValidFormat(newsgroup) {
		// invalid newsgroup format
		reason = "invalid newsgroup"
		return
	} else if banned, _ := daemon.database.NewsgroupBanned(newsgroup); banned {
		reason = "newsgroup banned"
		return
	} else if !(ValidMessageID(msgid) || (reference != "" && !ValidMessageID(reference))) {
		// invalid message id or reference
		reason = "invalid reference or message id is '" + msgid + "' reference is '" + reference + "'"
		return
	} else if daemon.database.ArticleBanned(msgid) {
		reason = "article banned"
	} else if reference != "" && daemon.database.ArticleBanned(reference) {
		reason = "thread banned"
	} else if daemon.database.HasArticleLocal(msgid) {
		// we already have this article locally
		reason = "have this article locally"
		return
	} else if daemon.database.HasArticle(msgid) {
		// we have already seen this article
		reason = "already seen"
		return
	} else if is_ctl {
		// we always allow control messages
		return
	} else if anon_poster {
		// this was posted anonymously
		if daemon.allow_anon {
			if has_attachment || is_signed {
				// this is a signed message or has attachment
				if daemon.allow_anon_attachments {
					// we'll allow anon attachments
					return
				} else {
					// we don't take signed messages or attachments posted anonymously
					reason = "no anon signed posts or attachments"
					return
				}
			} else {
				// we allow anon posts that are plain
				return
			}
		} else {
			// we don't allow anon posts of any kind
			reason = "no anon posts allowed"
			return
		}
	} else {
		// check for banned address
		var banned bool
		if encaddr != "" {
			banned, err = daemon.database.CheckEncIPBanned(encaddr)
			if err == nil {
				if banned {
					// this address is banned
					reason = "address banned"
					return
				} else {
					// not banned
					return
				}
			}
		} else {
			// idk wtf
			log.Println(self.name, "wtf? invalid article")
		}
	}
	return
}
예제 #20
0
파일: http.go 프로젝트: majestrate/nntpchan
func (h *httpWebhook) sendArticle(msgid nntp.MessageID, group nntp.Newsgroup) {
	f, err := h.storage.OpenArticle(msgid.String())
	if err == nil {
		u, _ := url.Parse(h.conf.URL)
		var r *http.Response
		var ctype string
		if h.conf.Dialect == "vichan" {
			c := textproto.NewConn(f)
			var hdr textproto.MIMEHeader
			hdr, err = c.ReadMIMEHeader()
			if err == nil {
				var body io.Reader
				ctype = hdr.Get("Content-Type")
				if ctype == "" || strings.HasPrefix(ctype, "text/plain") {
					ctype = "text/plain"
				}
				ctype = strings.Replace(strings.ToLower(ctype), "multipart/mixed", "multipart/form-data", 1)
				q := u.Query()
				for k, vs := range hdr {
					for _, v := range vs {
						q.Add(k, v)
					}
				}
				q.Set("Content-Type", ctype)
				u.RawQuery = q.Encode()

				if strings.HasPrefix(ctype, "multipart") {
					pr, pw := io.Pipe()
					log.Debug("using pipe")
					go func(in io.Reader, out io.WriteCloser) {
						_, params, _ := mime.ParseMediaType(ctype)
						if params == nil {
							// send as whatever lol
							io.Copy(out, in)
						} else {
							boundary, _ := params["boundary"]
							mpr := multipart.NewReader(in, boundary)
							mpw := multipart.NewWriter(out)
							mpw.SetBoundary(boundary)
							for {
								part, err := mpr.NextPart()
								if err == io.EOF {
									err = nil
									break
								} else if err == nil {
									// get part header
									h := part.Header
									// rewrite header part for php
									cd := h.Get("Content-Disposition")
									r := regexp.MustCompile(`filename="(.*)"`)
									// YOLO
									parts := r.FindStringSubmatch(cd)
									if len(parts) > 1 {
										fname := parts[1]
										h.Set("Content-Disposition", fmt.Sprintf(`filename="%s"; name="attachment[]"`, fname))
									}
									// make write part
									wp, err := mpw.CreatePart(h)
									if err == nil {
										// write part out
										io.Copy(wp, part)
									} else {
										log.Errorf("error writng webhook part: %s", err.Error())
									}
								}
								part.Close()
							}
							mpw.Close()
						}
						out.Close()
					}(c.R, pw)
					body = pr
				} else {
					body = f
				}
				r, err = http.Post(u.String(), ctype, body)
			}
		} else {
			var sz int64
			sz, err = f.Seek(0, 2)
			if err != nil {
				return
			}
			f.Seek(0, 0)
			// regular webhook
			ctype = "text/plain; charset=UTF-8"
			cl := new(http.Client)
			r, err = cl.Do(&http.Request{
				ContentLength: sz,
				URL:           u,
				Method:        "POST",
				Body:          f,
			})
		}
		if err == nil && r != nil {
			dec := json.NewDecoder(r.Body)
			result := make(map[string]interface{})
			err = dec.Decode(&result)
			if err == nil || err == io.EOF {
				msg, ok := result["error"]
				if ok {
					log.Warnf("hook gave error: %s", msg)
				} else {
					log.Debugf("hook response: %s", result)
				}
			} else {
				log.Warnf("hook response does not look like json: %s", err)
			}
			r.Body.Close()
			log.Infof("hook called for %s", msgid)
		}
	} else {
		f.Close()
	}
	if err != nil {
		log.Errorf("error calling web hook %s: %s", h.conf.Name, err.Error())
	}
}
예제 #21
0
파일: nntp.go 프로젝트: 4cdn/srndv2
func (self *nntpConnection) handleLine(daemon NNTPDaemon, code int, line string, conn *textproto.Conn) (err error) {
	parts := strings.Split(line, " ")
	var msgid string
	if code == 0 && len(parts) > 1 {
		msgid = parts[1]
	} else {
		msgid = parts[0]
	}
	if code == 238 {
		if ValidMessageID(msgid) {
			self.stream <- nntpTAKETHIS(msgid)
		}
		return
	} else if code == 239 {
		// successful TAKETHIS
		log.Println(msgid, "sent via", self.name)
		return
		// TODO: remember success
	} else if code == 431 {
		// CHECK said we would like this article later
		log.Println("defer sending", msgid, "to", self.name)
		go self.articleDefer(msgid)
	} else if code == 439 {
		// TAKETHIS failed
		log.Println(msgid, "was not sent to", self.name, "denied:", line)
		// TODO: remember denial
	} else if code == 438 {
		// they don't want the article
		// TODO: remeber rejection
	} else {
		// handle command
		parts := strings.Split(line, " ")
		if len(parts) > 1 {
			cmd := parts[0]
			if cmd == "MODE" {
				if parts[1] == "READER" {
					// reader mode
					self.mode = "READER"
					log.Println(self.name, "switched to reader mode")
					conn.PrintfLine("201 No posting Permitted")
				} else if parts[1] == "STREAM" {
					// wut? we're already in streaming mode
					log.Println(self.name, "already in streaming mode")
					conn.PrintfLine("203 Streaming enabled brah")
				} else {
					// invalid
					log.Println(self.name, "got invalid mode request", parts[1])
					conn.PrintfLine("501 invalid mode variant:", parts[1])
				}
			} else if cmd == "QUIT" {
				// quit command
				conn.PrintfLine("")
				// close our connection and return
				conn.Close()
				return
			} else if cmd == "CHECK" {
				// handle check command
				msgid := parts[1]
				// have we seen this article?
				if daemon.database.HasArticle(msgid) {
					// yeh don't want it
					conn.PrintfLine("438 %s", msgid)
				} else if daemon.database.ArticleBanned(msgid) {
					// it's banned we don't want it
					conn.PrintfLine("438 %s", msgid)
				} else {
					// yes we do want it and we don't have it
					conn.PrintfLine("238 %s", msgid)
				}
			} else if cmd == "TAKETHIS" {
				// handle takethis command
				var hdr textproto.MIMEHeader
				var reason string
				// read the article header
				hdr, err = conn.ReadMIMEHeader()
				if err == nil {
					// check the header
					reason, err = self.checkMIMEHeader(daemon, hdr)
					dr := conn.DotReader()
					if len(reason) > 0 {
						// discard, we do not want
						code = 439
						log.Println(self.name, "rejected", msgid, reason)
						_, err = io.Copy(ioutil.Discard, dr)
						err = daemon.database.BanArticle(msgid, reason)
					} else {
						// check if we don't have the rootpost
						reference := hdr.Get("References")
						newsgroup := hdr.Get("Newsgroups")
						if reference != "" && ValidMessageID(reference) && !daemon.store.HasArticle(reference) && !daemon.database.IsExpired(reference) {
							log.Println(self.name, "got reply to", reference, "but we don't have it")
							daemon.ask_for_article <- ArticleEntry{reference, newsgroup}
						}
						f := daemon.store.CreateTempFile(msgid)
						if f == nil {
							log.Println(self.name, "discarding", msgid, "we are already loading it")
							// discard
							io.Copy(ioutil.Discard, dr)
						} else {
							// write header
							err = writeMIMEHeader(f, hdr)
							// write body
							_, err = io.Copy(f, dr)
							if err == nil || err == io.EOF {
								f.Close()
								// we gud, tell daemon
								daemon.infeed_load <- msgid
							} else {
								log.Println(self.name, "error reading message", err)
							}
						}
						code = 239
						reason = "gotten"
					}
				} else {
					log.Println(self.name, "error reading mime header:", err)
					code = 439
					reason = "error reading mime header"
				}
				conn.PrintfLine("%d %s %s", code, msgid, reason)
			} else if cmd == "ARTICLE" {
				if ValidMessageID(msgid) {
					if daemon.store.HasArticle(msgid) {
						// we have it yeh
						f, err := os.Open(daemon.store.GetFilename(msgid))
						if err == nil {
							conn.PrintfLine("220 %s", msgid)
							dw := conn.DotWriter()
							_, err = io.Copy(dw, f)
							dw.Close()
							f.Close()
						} else {
							// wtf?!
							conn.PrintfLine("503 idkwtf happened: %s", err.Error())
						}
					} else {
						// we dont got it
						conn.PrintfLine("430 %s", msgid)
					}
				} else {
					// invalid id
					conn.PrintfLine("500 Syntax error")
				}
			} else if cmd == "POST" {
				// handle POST command
				conn.PrintfLine("340 Post it nigguh; end with <CR-LF>.<CR-LF>")
				hdr, err := conn.ReadMIMEHeader()
				var success bool
				if err == nil {
					hdr["Message-ID"] = []string{genMessageID(daemon.instance_name)}
					reason, err := self.checkMIMEHeader(daemon, hdr)
					success = reason == "" && err == nil
					if success {
						dr := conn.DotReader()
						reference := hdr.Get("References")
						newsgroup := hdr.Get("Newsgroups")
						if reference != "" && ValidMessageID(reference) && !daemon.store.HasArticle(reference) && !daemon.database.IsExpired(reference) {
							log.Println(self.name, "got reply to", reference, "but we don't have it")
							daemon.ask_for_article <- ArticleEntry{reference, newsgroup}
						}
						f := daemon.store.CreateTempFile(msgid)
						if f == nil {
							log.Println(self.name, "discarding", msgid, "we are already loading it")
							// discard
							io.Copy(ioutil.Discard, dr)
						} else {
							// write header
							err = writeMIMEHeader(f, hdr)
							// write body
							_, err = io.Copy(f, dr)
							if err == nil || err == io.EOF {
								f.Close()
								// we gud, tell daemon
								daemon.infeed_load <- msgid
							} else {
								log.Println(self.name, "error reading message", err)
							}
						}
					}
				}
				if success && err == nil {
					// all gud
					conn.PrintfLine("240 We got it, thnkxbai")
				} else {
					// failed posting
					if err != nil {
						log.Println(self.name, "failed nntp POST", err)
					}
					conn.PrintfLine("441 Posting Failed")
				}
			} else if cmd == "IHAVE" {
				// handle IHAVE command
				msgid := parts[1]
				if daemon.database.HasArticleLocal(msgid) || daemon.database.HasArticle(msgid) || daemon.database.ArticleBanned(msgid) {
					// we don't want it
					conn.PrintfLine("435 Article Not Wanted")
				} else {
					// gib we want
					conn.PrintfLine("335 Send it plz")
					hdr, err := conn.ReadMIMEHeader()
					if err == nil {
						// check the header
						var reason string
						reason, err = self.checkMIMEHeader(daemon, hdr)
						dr := conn.DotReader()
						if len(reason) > 0 {
							// discard, we do not want
							log.Println(self.name, "rejected", msgid, reason)
							_, err = io.Copy(ioutil.Discard, dr)
							// ignore this
							_ = daemon.database.BanArticle(msgid, reason)
							conn.PrintfLine("437 Rejected do not send again bro")
						} else {
							// check if we don't have the rootpost
							reference := hdr.Get("References")
							newsgroup := hdr.Get("Newsgroups")
							if reference != "" && ValidMessageID(reference) && !daemon.store.HasArticle(reference) && !daemon.database.IsExpired(reference) {
								log.Println(self.name, "got reply to", reference, "but we don't have it")
								daemon.ask_for_article <- ArticleEntry{reference, newsgroup}
							}
							f := daemon.store.CreateTempFile(msgid)
							if f == nil {
								log.Println(self.name, "discarding", msgid, "we are already loading it")
								// discard
								io.Copy(ioutil.Discard, dr)
							} else {
								// write header
								err = writeMIMEHeader(f, hdr)
								// write body
								_, err = io.Copy(f, dr)
								if err == nil || err == io.EOF {
									f.Close()
									// we gud, tell daemon
									daemon.infeed_load <- msgid
								} else {
									log.Println(self.name, "error reading message", err)
								}
							}
							conn.PrintfLine("235 We got it")
						}
					} else {
						// error here
						conn.PrintfLine("436 Transfer failed: " + err.Error())
					}
				}
			} else if cmd == "NEWSGROUPS" {
				// handle NEWSGROUPS
				conn.PrintfLine("231 List of newsgroups follow")
				dw := conn.DotWriter()
				// get a list of every newsgroup
				groups := daemon.database.GetAllNewsgroups()
				// for each group
				for _, group := range groups {
					// get low/high water mark
					lo, hi, err := daemon.database.GetLastAndFirstForGroup(group)
					if err == nil {
						// XXX: we ignore errors here :\
						_, _ = io.WriteString(dw, fmt.Sprintf("%s %d %d y\n", group, lo, hi))
					} else {
						log.Println(self.name, "could not get low/high water mark for", group, err)
					}
				}
				// flush dotwriter
				dw.Close()

			} else if cmd == "XOVER" {
				// handle XOVER
				if self.group == "" {
					conn.PrintfLine("412 No newsgroup selected")
				} else {
					// handle xover command
					// right now it's every article in group
					models, err := daemon.database.GetPostsInGroup(self.group)
					if err == nil {
						conn.PrintfLine("224 Overview information follows")
						dw := conn.DotWriter()
						for idx, model := range models {
							io.WriteString(dw, fmt.Sprintf("%.6d\t%s\t\"%s\" <%s@%s>\t%s\t%s\t%s\r\n", idx+1, model.Subject(), model.Name(), model.Name(), model.Frontend(), model.Date(), model.MessageID(), model.Reference()))
						}
						dw.Close()
					} else {
						log.Println(self.name, "error when getting posts in", self.group, err)
						conn.PrintfLine("500 error, %s", err.Error())
					}
				}
			} else if cmd == "GROUP" {
				// handle GROUP command
				group := parts[1]
				// check for newsgroup
				if daemon.database.HasNewsgroup(group) {
					// we have the group
					self.group = group
					// count posts
					number := daemon.database.CountPostsInGroup(group, 0)
					// get hi/low water marks
					low, hi, err := daemon.database.GetLastAndFirstForGroup(group)
					if err == nil {
						// we gud
						conn.PrintfLine("211 %d %d %d %s", number, low, hi, group)
					} else {
						// wtf error
						log.Println(self.name, "error in GROUP command", err)
						// still have to reply, send it bogus low/hi
						conn.PrintfLine("211 %d 0 1 %s", number, group)
					}
				} else {
					// no such group
					conn.PrintfLine("411 No Such Newsgroup")
				}
			} else {
				log.Println(self.name, "invalid command recv'd", cmd)
				conn.PrintfLine("500 Invalid command: %s", cmd)
			}
		}
	}
	return
}
예제 #22
0
파일: nntp.go 프로젝트: ZiRo-/srndv2
func (self *nntpConnection) handleLine(daemon *NNTPDaemon, code int, line string, conn *textproto.Conn) (err error) {
	parts := strings.Split(line, " ")
	var msgid string
	if code == 0 && len(parts) > 1 {
		msgid = parts[1]
	} else {
		msgid = parts[0]
	}
	if code == 238 {
		if ValidMessageID(msgid) {
			self.messageSetPendingState(msgid, "takethis")
			// they want this article
			self.takethis <- msgid
		}
		return
	} else if code == 239 {
		// successful TAKETHIS
		log.Println(msgid, "sent via", self.name)
		self.messageSetProcessed(msgid)
		return
		// TODO: remember success
	} else if code == 431 {
		// CHECK said we would like this article later
		self.messageSetProcessed(msgid)
	} else if code == 439 {
		// TAKETHIS failed
		log.Println(msgid, "was not sent to", self.name, "denied:", line)
		self.messageSetProcessed(msgid)
		// TODO: remember denial
	} else if code == 438 {
		// they don't want the article
		// TODO: remeber rejection
		self.messageSetProcessed(msgid)
	} else {
		// handle command
		parts := strings.Split(line, " ")
		if len(parts) > 1 {
			cmd := strings.ToUpper(parts[0])
			if cmd == "MODE" {
				mode := strings.ToUpper(parts[1])
				if mode == "READER" {
					// reader mode
					self.mode = "READER"
					log.Println(self.name, "switched to reader mode")
					if self.authenticated {
						conn.PrintfLine("200 Posting Permitted")
					} else {
						conn.PrintfLine("201 No posting Permitted")
					}
				} else if mode == "STREAM" && self.authenticated {
					// wut? we're already in streaming mode
					log.Println(self.name, "already in streaming mode")
					conn.PrintfLine("203 Streaming enabled brah")
				} else {
					// invalid
					log.Println(self.name, "got invalid mode request", parts[1])
					conn.PrintfLine("501 invalid mode variant:", parts[1])
				}
			} else if cmd == "QUIT" {
				// quit command
				conn.PrintfLine("")
				// close our connection and return
				conn.Close()
				return

			} else if cmd == "AUTHINFO" {
				if len(parts) > 1 {
					auth_cmd := strings.ToUpper(parts[1])
					if auth_cmd == "USER" {
						// first part
						self.username = parts[2]
						// next phase is PASS
						conn.PrintfLine("381 Password required")
					} else if auth_cmd == "PASS" {
						if len(self.username) == 0 {
							conn.PrintfLine("482 Authentication commands issued out of sequence")
						} else {
							// try login
							var valid bool
							valid, err = daemon.database.CheckNNTPUserExists(self.username)
							if valid {
								valid, err = daemon.database.CheckNNTPLogin(self.username, line[14:])
							}
							if valid {
								// valid login
								self.authenticated = true
								conn.PrintfLine("281 Authentication accepted")
							} else if err == nil {
								// invalid login
								conn.PrintfLine("481 Authentication rejected")
							} else {
								// there was an error
								// logit
								log.Println(self.name, "error while logging in as", self.username, err)
								conn.PrintfLine("501 error while logging in")
							}
						}
					}
				} else {
					// wut ?
					// wrong legnth of parametrs
				}
			} else if cmd == "CHECK" {
				// handle check command
				msgid := parts[1]
				if self.mode != "STREAM" {
					// we can't we are not in streaming mode
					conn.PrintfLine("431 %s", msgid)
					return
				}
				// have we seen this article?
				if daemon.database.HasArticle(msgid) {
					// yeh don't want it
					conn.PrintfLine("438 %s", msgid)
				} else if daemon.database.ArticleBanned(msgid) {
					// it's banned we don't want it
					conn.PrintfLine("438 %s", msgid)
				} else {
					// yes we do want it and we don't have it
					conn.PrintfLine("238 %s", msgid)
				}
			} else if cmd == "TAKETHIS" {
				// handle takethis command
				var hdr textproto.MIMEHeader
				var reason string
				var ban bool
				// read the article header
				r := bufio.NewReader(conn.DotReader())
				hdr, err = readMIMEHeader(r)
				if err == nil {
					// check the header
					reason, ban, err = self.checkMIMEHeader(daemon, hdr)
					if len(reason) > 0 {
						// discard, we do not want
						code = 439
						log.Println(self.name, "rejected", msgid, reason)
						_, err = io.Copy(ioutil.Discard, r)
						if ban {
							err = daemon.database.BanArticle(msgid, reason)
						}
					} else if err == nil {
						// check if we don't have the rootpost
						reference := hdr.Get("References")
						newsgroup := hdr.Get("Newsgroups")
						if reference != "" && ValidMessageID(reference) && !daemon.store.HasArticle(reference) && !daemon.database.IsExpired(reference) {
							log.Println(self.name, "got reply to", reference, "but we don't have it")
							go daemon.askForArticle(ArticleEntry{reference, newsgroup})
						}
						// store message
						err = self.storeMessage(daemon, hdr, r)
						if err == nil {
							code = 239
							reason = "gotten"
						} else {
							code = 439
							reason = err.Error()
						}
					} else {
						// error?
						// discard, we do not want
						code = 439
						log.Println(self.name, "rejected", msgid, reason)
						_, err = io.Copy(ioutil.Discard, r)
						if ban {
							err = daemon.database.BanArticle(msgid, reason)
						}
					}
				} else {
					log.Println(self.name, "error reading mime header:", err)
					code = 439
					reason = "error reading mime header"
				}
				conn.PrintfLine("%d %s %s", code, msgid, reason)
			} else if cmd == "ARTICLE" {
				if !ValidMessageID(msgid) {
					if len(self.group) > 0 {
						n, err := strconv.Atoi(msgid)
						if err == nil {
							msgid, err = daemon.database.GetMessageIDForNNTPID(self.group, int64(n))
						}
					}
				}
				if ValidMessageID(msgid) && daemon.store.HasArticle(msgid) {
					// we have it yeh
					f, err := daemon.store.OpenMessage(msgid)
					if err == nil {
						conn.PrintfLine("220 %s", msgid)
						dw := conn.DotWriter()
						_, err = io.Copy(dw, f)
						dw.Close()
						f.Close()
					} else {
						// wtf?!
						conn.PrintfLine("503 idkwtf happened: %s", err.Error())
					}
				} else {
					// we dont got it
					conn.PrintfLine("430 %s", msgid)
				}
			} else if cmd == "IHAVE" {
				if !self.authenticated {
					conn.PrintfLine("483 You have not authenticated")
				} else {
					// handle IHAVE command
					msgid := parts[1]
					if daemon.database.HasArticleLocal(msgid) || daemon.database.HasArticle(msgid) || daemon.database.ArticleBanned(msgid) {
						// we don't want it
						conn.PrintfLine("435 Article Not Wanted")
					} else {
						// gib we want
						conn.PrintfLine("335 Send it plz")
						r := bufio.NewReader(conn.DotReader())
						hdr, err := readMIMEHeader(r)
						if err == nil {
							// check the header
							var reason string
							var ban bool
							reason, ban, err = self.checkMIMEHeader(daemon, hdr)
							if len(reason) > 0 {
								// discard, we do not want
								log.Println(self.name, "rejected", msgid, reason)
								_, err = io.Copy(ioutil.Discard, r)
								if ban {
									_ = daemon.database.BanArticle(msgid, reason)
								}
								conn.PrintfLine("437 Rejected do not send again bro")
							} else {
								// check if we don't have the rootpost
								reference := hdr.Get("References")
								newsgroup := hdr.Get("Newsgroups")
								if reference != "" && ValidMessageID(reference) && !daemon.store.HasArticle(reference) && !daemon.database.IsExpired(reference) {
									log.Println(self.name, "got reply to", reference, "but we don't have it")
									go daemon.askForArticle(ArticleEntry{reference, newsgroup})
								}
								err = self.storeMessage(daemon, hdr, r)
								if err == nil {
									conn.PrintfLine("235 We got it")
								} else {
									conn.PrintfLine("437 Transfer Failed %s", err.Error())
								}
							}
						} else {
							// error here
							conn.PrintfLine("436 Transfer failed: " + err.Error())
						}
					}
				}
			} else if cmd == "LISTGROUP" {
				// handle LISTGROUP
				var group string
				if len(parts) > 1 {
					// parameters
					group = parts[1]
				} else {
					group = self.group
				}
				if len(group) > 0 && newsgroupValidFormat(group) {
					if daemon.database.HasNewsgroup(group) {
						// we has newsgroup
						var hi, lo int64
						count, err := daemon.database.CountAllArticlesInGroup(group)
						if err == nil {
							hi, lo, err = daemon.database.GetLastAndFirstForGroup(group)
							if err == nil {
								conn.PrintfLine("211 %d %d %d %s list follows", count, lo, hi, group)
								dw := conn.DotWriter()
								idx := lo
								for idx <= hi {
									fmt.Fprintf(dw, "%d\r\n", idx)
									idx++
								}
								dw.Close()
							}
						}
						if err != nil {
							log.Println("LISTGROUP fail", err)
							conn.PrintfLine("500 error in LISTGROUP: %s", err.Error())
						}
					} else {
						// don't has newsgroup
						conn.PrintfLine("411 no such newsgroup")
					}
				} else {
					conn.PrintfLine("412 no newsgroup selected")
				}
			} else if cmd == "NEWSGROUPS" {
				// handle NEWSGROUPS
				conn.PrintfLine("231 List of newsgroups follow")
				dw := conn.DotWriter()
				// get a list of every newsgroup
				groups := daemon.database.GetAllNewsgroups()
				// for each group
				for _, group := range groups {
					// get low/high water mark
					lo, hi, err := daemon.database.GetLastAndFirstForGroup(group)
					if err == nil {
						// XXX: we ignore errors here :\
						_, _ = io.WriteString(dw, fmt.Sprintf("%s %d %d y\n", group, lo, hi))
					} else {
						log.Println(self.name, "could not get low/high water mark for", group, err)
					}
				}
				// flush dotwriter
				dw.Close()

			} else if cmd == "XOVER" {
				// handle XOVER
				if self.group == "" {
					conn.PrintfLine("412 No newsgroup selected")
				} else {
					// handle xover command
					// right now it's every article in group
					models, err := daemon.database.GetPostsInGroup(self.group)
					if err == nil {
						conn.PrintfLine("224 Overview information follows")
						dw := conn.DotWriter()
						for idx, model := range models {
							io.WriteString(dw, fmt.Sprintf("%.6d\t%s\t\"%s\" <%s@%s>\t%s\t%s\t%s\r\n", idx+1, model.Subject(), model.Name(), model.Name(), model.Frontend(), model.Date(), model.MessageID(), model.Reference()))
						}
						dw.Close()
					} else {
						log.Println(self.name, "error when getting posts in", self.group, err)
						conn.PrintfLine("500 error, %s", err.Error())
					}
				}
			} else if cmd == "HEAD" {
				if len(self.group) == 0 {
					// no group selected
					conn.PrintfLine("412 No newsgroup slected")
				} else {
					// newsgroup is selected
					// handle HEAD command
					if len(parts) == 0 {
						// we have no parameters
						if len(self.selected_article) > 0 {
							// we have a selected article
						} else {
							// no selected article
							conn.PrintfLine("420 current article number is invalid")
						}
					} else {
						// head command has 1 or more paramters
						var n int64
						var msgid string
						var has bool
						var code int
						n, err = strconv.ParseInt(parts[1], 10, 64)
						if err == nil {
							// is a number
							msgid, err = daemon.database.GetMessageIDForNNTPID(self.group, n)
							if err == nil && len(msgid) > 0 {
								has = daemon.database.HasArticleLocal(msgid)
							}
							if !has {
								code = 423
							}
						} else if ValidMessageID(parts[1]) {
							msgid = parts[1]
							has = daemon.database.HasArticleLocal(msgid)
							if has {
								n, err = daemon.database.GetNNTPIDForMessageID(self.group, parts[1])
							} else {
								code = 430
							}
						}
						if err == nil {
							if has {
								// we has
								hdrs := daemon.store.GetHeaders(msgid)
								if hdrs == nil {
									// wtf can't load?
									conn.PrintfLine("500 cannot load headers")
								} else {
									// headers loaded, send them
									conn.PrintfLine("221 %d %s", n, msgid)
									dw := conn.DotWriter()
									err = writeMIMEHeader(dw, hdrs)
									dw.Close()
									hdrs = nil
								}
							} else if code > 0 {
								// don't has
								conn.PrintfLine("%d don't have article", code)
							} else {
								// invalid state
								conn.PrintfLine("500 invalid state in HEAD, should have article but we don't")
							}
						} else {
							// error occured
							conn.PrintfLine("500 error in HEAD: %s", err.Error())
						}
					}
				}
			} else if cmd == "GROUP" {
				// handle GROUP command
				group := parts[1]
				// check for newsgroup
				if daemon.database.HasNewsgroup(group) {
					// we have the group
					self.group = group
					// count posts
					number := daemon.database.CountPostsInGroup(group, 0)
					// get hi/low water marks
					hi, low, err := daemon.database.GetLastAndFirstForGroup(group)
					if err == nil {
						// we gud
						conn.PrintfLine("211 %d %d %d %s", number, low, hi, group)
					} else {
						// wtf error
						log.Println(self.name, "error in GROUP command", err)
						// still have to reply, send it bogus low/hi
						conn.PrintfLine("211 %d 0 1 %s", number, group)
					}
				} else {
					// no such group
					conn.PrintfLine("411 No Such Newsgroup")
				}
			} else if cmd == "LIST" && parts[1] == "NEWSGROUPS" {
				conn.PrintfLine("215 list of newsgroups follows")
				// handle list command
				groups := daemon.database.GetAllNewsgroups()
				dw := conn.DotWriter()
				for _, group := range groups {
					last, first, err := daemon.database.GetLastAndFirstForGroup(group)
					if err == nil {
						io.WriteString(dw, fmt.Sprintf("%s %d %d y\r\n", group, first, last))
					} else {
						log.Println("cannot get last/first ids for group", group, err)
					}
				}
				dw.Close()
			} else if cmd == "STAT" {
				if len(self.group) == 0 {
					if len(parts) == 2 {
						// parameter given
						msgid := parts[1]
						// check for article
						if ValidMessageID(msgid) && daemon.database.HasArticleLocal(msgid) {
							// valid message id
							var n int64
							n, err = daemon.database.GetNNTPIDForMessageID(self.group, msgid)
							// exists
							conn.PrintfLine("223 %d %s", n, msgid)
							err = nil
						} else {
							conn.PrintfLine("430 No article with that message-id")
						}
					} else {
						conn.PrintfLine("412 No newsgroup selected")
					}
				} else if daemon.database.HasNewsgroup(self.group) {
					// group specified
					if len(parts) == 2 {
						// parameter specified
						var msgid string
						var n int64
						n, err = strconv.ParseInt(parts[1], 10, 64)
						if err == nil {
							msgid, err = daemon.database.GetMessageIDForNNTPID(self.group, n)
							if err != nil {
								// error getting id
								conn.PrintfLine("500 error getting nntp article id: %s", err.Error())
								return
							}
						} else {
							// message id
							msgid = parts[1]
						}
						if ValidMessageID(msgid) && daemon.database.HasArticleLocal(msgid) {
							conn.PrintfLine("223 %d %s", n, msgid)
						} else if n == 0 {
							// was a message id
							conn.PrintfLine("430 no such article")
						} else {
							// was an article number
							conn.PrintfLine("423 no article with that number")
						}
					} else {
						conn.PrintfLine("420 Current article number is invalid")
					}
				} else {
					conn.PrintfLine("500 invalid daemon state, got STAT with group set but we don't have that group now?")
				}
			} else {
				log.Println(self.name, "invalid command recv'd", cmd)
				conn.PrintfLine("500 Invalid command: %s", cmd)
			}
		} else {
			if line == "LIST" {
				conn.PrintfLine("215 list of newsgroups follows")
				// handle list command
				groups := daemon.database.GetAllNewsgroups()
				dw := conn.DotWriter()
				for _, group := range groups {
					last, first, err := daemon.database.GetLastAndFirstForGroup(group)
					if err == nil {
						io.WriteString(dw, fmt.Sprintf("%s %d %d y\r\n", group, first, last))
					} else {
						log.Println("cannot get last/first ids for group", group, err)
					}
				}
				dw.Close()
			} else if line == "POST" {
				if !self.authenticated {
					// needs tls to work if not logged in
					conn.PrintfLine("440 Posting Not Allowed")
				} else {
					// handle POST command
					conn.PrintfLine("340 Yeeeh postit yo; end with <CR-LF>.<CR-LF>")
					var hdr textproto.MIMEHeader
					hdr, err = readMIMEHeader(conn.R)
					var success, gotten bool
					var reason string
					if err == nil {
						if getMessageID(hdr) == "" {
							hdr.Set("Message-ID", genMessageID(daemon.instance_name))
						}
						msgid = getMessageID(hdr)
						hdr.Set("Date", timeNowStr())
						ipaddr, _, _ := net.SplitHostPort(self.addr.String())
						if len(ipaddr) > 0 {
							// inject encrypted ip for poster
							encaddr, err := daemon.database.GetEncAddress(ipaddr)
							if err == nil {
								hdr.Set("X-Encrypted-Ip", encaddr)
							}
						}
						reason, _, err = self.checkMIMEHeader(daemon, hdr)
						success = reason == "" && err == nil
						if success {
							r := bufio.NewReader(conn.DotReader())
							reference := hdr.Get("References")
							newsgroup := hdr.Get("Newsgroups")
							if reference != "" && ValidMessageID(reference) {
								if !daemon.store.HasArticle(reference) && !daemon.database.IsExpired(reference) {
									log.Println(self.name, "got reply to", reference, "but we don't have it")
									go daemon.askForArticle(ArticleEntry{reference, newsgroup})
								}
							} else if reference != "" {
								// bad message id
								reason = "cannot reply with invalid reference, maybe you are replying to a reply?"
								success = false
							}
							if success && daemon.database.HasNewsgroup(newsgroup) {
								err = self.storeMessage(daemon, hdr, r)
							}
						}
					}
					if success && gotten && err == nil {
						// all gud
						conn.PrintfLine("240 We got it, thnkxbai")
					} else {
						// failed posting
						if err != nil {
							log.Println(self.name, "failed nntp POST", err)
						}
						conn.PrintfLine("441 Posting Failed %s", reason)
					}
				}
			} else {
				conn.PrintfLine("500 wut?")
			}
		}
	}
	return
}
예제 #23
0
func parseHeadersAndStream(header textproto.MIMEHeader, body io.ReadCloser) GetObjectResult {
	objectId, err := strconv.ParseInt(header.Get("Object-ID"), 10, 64)
	if err != nil {
		// Attempt to parse a Rets Response code (if it exists)
		retsResp, parseErr := ParseRetsResponse(body)
		if parseErr != nil {
			return GetObjectResult{nil, err}
		}
		// Include a GetObject (empty of content) so that its rets response can be retrieved
		emptyResult := GetObject{
			RetsErrorMessage: *retsResp,
			RetsError:        retsResp.ReplyCode != 0,
		}
		return GetObjectResult{&emptyResult, err}
	}
	preferred, err := strconv.ParseBool(header.Get("Preferred"))
	if err != nil {
		preferred = false
	}
	objectData := make(map[string]string)
	for _, v := range header[textproto.CanonicalMIMEHeaderKey("ObjectData")] {
		kv := strings.Split(v, "=")
		objectData[kv[0]] = kv[1]
	}
	blob, err := ioutil.ReadAll(body)
	if err != nil {
		return GetObjectResult{nil, err}
	}

	retsError, err := strconv.ParseBool(header.Get("RETS-Error"))
	retsErrorMsg := &RetsResponse{0, "Success"}
	switch {
	case err != nil:
		retsError = false
	case retsError:
		body := ioutil.NopCloser(bytes.NewReader([]byte(blob)))
		retsErrorMsg, err = ParseRetsResponse(body)
		if err != nil {
			return GetObjectResult{nil, err}
		}
	}

	object := GetObject{
		// required
		ObjectId:    int(objectId),
		ContentId:   header.Get("Content-ID"),
		ContentType: header.Get("Content-Type"),
		// optional
		Uid:              header.Get("UID"),
		Description:      header.Get("Content-Description"),
		SubDescription:   header.Get("Content-Sub-Description"),
		Location:         header.Get("Location"),
		RetsError:        retsError,
		RetsErrorMessage: *retsErrorMsg,
		Preferred:        preferred,
		ObjectData:       objectData,
		Blob:             blob,
	}

	return GetObjectResult{&object, nil}
}